docs: document Android and friends (#5415)

These are experimental, currently available through `_android`.
This commit is contained in:
Dmitry Gozman 2021-02-11 10:31:57 -08:00 committed by GitHub
parent 44ff8b518b
commit 99f8e1cf63
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
22 changed files with 1645 additions and 235 deletions

View File

@ -0,0 +1,81 @@
# class: Android
* langs: js
Playwright has **experimental** support for Android automation. You can access android namespace via:
```js
const { _android } = require('playwright');
```
An example of the Android automation script would be:
```js
const { _android } = require('playwright');
(async () => {
// Connect to the device.
const [device] = await playwright._android.devices();
console.log(`Model: ${device.model()}`);
console.log(`Serial: ${device.serial()}`);
// Take screenshot of the whole device.
await device.screenshot({ path: 'device.png' });
{
// --------------------- WebView -----------------------
// Launch an application with WebView.
await device.shell('am force-stop org.chromium.webview_shell');
await device.shell('am start org.chromium.webview_shell/.WebViewBrowserActivity');
// Get the WebView.
const webview = await device.webView({ pkg: 'org.chromium.webview_shell' });
// Fill the input box.
await device.fill({ res: 'org.chromium.webview_shell:id/url_field' }, 'github.com/microsoft/playwright');
await device.press({ res: 'org.chromium.webview_shell:id/url_field' }, 'Enter');
// Work with WebView's page as usual.
const page = await webview.page();
await page.page.waitForNavigation({ url: /.*microsoft\/playwright.*/ });
console.log(await page.title());
}
{
// --------------------- Browser -----------------------
// Launch Chrome browser.
await device.shell('am force-stop com.android.chrome');
const context = await device.launchBrowser();
// Use BrowserContext as usual.
const page = await context.newPage();
await page.goto('https://webkit.org/');
console.log(await page.evaluate(() => window.location.href));
await page.screenshot({ path: 'page.png' });
await context.close();
}
// Close the device.
await device.close();
})();
```
Note that since you don't need Playwright to install web browsers when testing Android, you can omit browser download via setting the following environment variable when installing Playwright:
```sh js
$ PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD=1 npm i -D playwright
```
## async method: Android.devices
- returns: <[Array]<[AndroidDevice]>>
Returns the list of detected Android devices.
## method: Android.setDefaultTimeout
This setting will change the default maximum time for all the methods accepting [`param: timeout`] option.
### param: Android.setDefaultTimeout.timeout
- `timeout` <[float]>
Maximum time in milliseconds

View File

@ -0,0 +1,368 @@
# class: AndroidDevice
* langs: js
[AndroidDevice] represents a connected device, either real hardware or emulated. Devices can be obtained using [`method: Android.devices`].
## event: AndroidDevice.webView
- type: <[AndroidWebView]>
Emitted when a new WebView instance is detected.
## async method: AndroidDevice.close
Disconnects from the device.
## async method: AndroidDevice.drag
Drags the widget defined by [`param: selector`] towards [`param: dest`] point.
### param: AndroidDevice.drag.selector
- `selector` <[AndroidSelector]>
Selector to drag.
### param: AndroidDevice.drag.dest
- `dest` <[Object]>
- `x` <[float]>
- `y` <[float]>
Point to drag to.
### option: AndroidDevice.drag.speed
- `speed` <[float]>
Optional speed of the drag in pixels per second.
### option: AndroidDevice.drag.timeout = %%-android-timeout-%%
## async method: AndroidDevice.fill
Fills the specific [`param: selector`] input box with [`param: text`].
### param: AndroidDevice.fill.selector
- `selector` <[AndroidSelector]>
Selector to fill.
### param: AndroidDevice.fill.text
- `text` <[string]>
Text to be filled in the input box.
### option: AndroidDevice.fill.timeout = %%-android-timeout-%%
## async method: AndroidDevice.fling
Flings the widget defined by [`param: selector`] in the specified [`param: direction`].
### param: AndroidDevice.fling.selector
- `selector` <[AndroidSelector]>
Selector to fling.
### param: AndroidDevice.fling.direction
- `direction` <[AndroidFlingDirection]<"down"|"up"|"left"|"right">>
Fling direction.
### option: AndroidDevice.fling.speed
- `speed` <[float]>
Optional speed of the fling in pixels per second.
### option: AndroidDevice.fling.timeout = %%-android-timeout-%%
## async method: AndroidDevice.info
- returns: <[AndroidElementInfo]>
Returns information about a widget defined by [`param: selector`].
### param: AndroidDevice.info.selector
- `selector` <[AndroidSelector]>
Selector to return information about.
## property: AndroidDevice.input
- type: <[AndroidInput]>
## async method: AndroidDevice.installApk
Installs an apk on the device.
### param: AndroidDevice.installApk.file
- `file` <[string]|[Buffer]>
Either a path to the apk file, or apk file content.
### option: AndroidDevice.installApk.args
- `args` <[Array]<[string]>>
Optional arguments to pass to the `shell:cmd package install` call. Defaults to `-r -t -S`.
## async method: AndroidDevice.launchBrowser
- returns: <[ChromiumBrowserContext]>
Launches Chrome browser on the device, and returns its persistent context.
### option: AndroidDevice.launchBrowser.pkg
- `command` <[string]>
Optional package name to launch instead of default Chrome for Android.
### option: AndroidDevice.launchBrowser.-inline- = %%-shared-context-params-list-%%
## async method: AndroidDevice.longTap
Performs a long tap on the widget defined by [`param: selector`].
### param: AndroidDevice.longTap.selector
- `selector` <[AndroidSelector]>
Selector to tap on.
### option: AndroidDevice.longTap.timeout = %%-android-timeout-%%
## method: AndroidDevice.model
- returns: <[string]>
Device model.
## async method: AndroidDevice.open
- returns: <[AndroidSocket]>
Launches a process in the shell on the device and returns a socket to communicate with the launched process.
### param: AndroidDevice.open.command
- `command` <[string]> Shell command to execute.
## async method: AndroidDevice.pinchClose
Pinches the widget defined by [`param: selector`] in the closing direction.
### param: AndroidDevice.pinchClose.selector
- `selector` <[AndroidSelector]>
Selector to pinch close.
### param: AndroidDevice.pinchClose.percent
- `percent` <[float]>
The size of the pinch as a percentage of the widget's size.
### option: AndroidDevice.pinchClose.speed
- `speed` <[float]>
Optional speed of the pinch in pixels per second.
### option: AndroidDevice.pinchClose.timeout = %%-android-timeout-%%
## async method: AndroidDevice.pinchOpen
Pinches the widget defined by [`param: selector`] in the open direction.
### param: AndroidDevice.pinchOpen.selector
- `selector` <[AndroidSelector]>
Selector to pinch open.
### param: AndroidDevice.pinchOpen.percent
- `percent` <[float]>
The size of the pinch as a percentage of the widget's size.
### option: AndroidDevice.pinchOpen.speed
- `speed` <[float]>
Optional speed of the pinch in pixels per second.
### option: AndroidDevice.pinchOpen.timeout = %%-android-timeout-%%
## async method: AndroidDevice.press
Presses the specific [`param: key`] in the widget defined by [`param: selector`].
### param: AndroidDevice.press.selector
- `selector` <[AndroidSelector]>
Selector to press the key in.
### param: AndroidDevice.press.key
- `key` <[AndroidKey]>
The key to press.
### option: AndroidDevice.press.timeout = %%-android-timeout-%%
## async method: AndroidDevice.push
Copies a file to the device.
### param: AndroidDevice.push.file
- `file` <[string]|[Buffer]>
Either a path to the file, or file content.
### param: AndroidDevice.push.path
- `path` <[string]>
Path to the file on the device.
### option: AndroidDevice.push.mode
- `mode` <[int]>
Optional file mode, defaults to `644` (`rw-r--r--`).
## async method: AndroidDevice.screenshot
- returns: <[Buffer]>
Returns the buffer with the captured screenshot of the device.
### option: AndroidDevice.screenshot.path
- `path` <[path]>
The file path to save the image to. If [`option: path`] is a
relative path, then it is resolved relative to the current working directory. If no path is provided, the image won't be
saved to the disk.
## async method: AndroidDevice.scroll
Scrolls the widget defined by [`param: selector`] in the specified [`param: direction`].
### param: AndroidDevice.scroll.selector
- `selector` <[AndroidSelector]>
Selector to scroll.
### param: AndroidDevice.scroll.direction
- `direction` <[AndroidScrollDirection]<"down"|"up"|"left"|"right">>
Scroll direction.
### param: AndroidDevice.scroll.percent
- `percent` <[float]>
Distance to scroll as a percentage of the widget's size.
### option: AndroidDevice.scroll.speed
- `speed` <[float]>
Optional speed of the scroll in pixels per second.
### option: AndroidDevice.scroll.timeout = %%-android-timeout-%%
## method: AndroidDevice.serial
- returns: <[string]>
Device serial number.
## method: AndroidDevice.setDefaultTimeout
This setting will change the default maximum time for all the methods accepting [`param: timeout`] option.
### param: AndroidDevice.setDefaultTimeout.timeout
- `timeout` <[float]>
Maximum time in milliseconds
## async method: AndroidDevice.shell
- returns: <[Buffer]>
Executes a shell command on the device and returns its output.
### param: AndroidDevice.shell.command
- `command` <[string]>
Shell command to execute.
## async method: AndroidDevice.swipe
Swipes the widget defined by [`param: selector`] in the specified [`param: direction`].
### param: AndroidDevice.swipe.selector
- `selector` <[AndroidSelector]>
Selector to swipe.
### param: AndroidDevice.swipe.direction
- `direction` <[AndroidSwipeDirection]<"down"|"up"|"left"|"right">>
Swipe direction.
### param: AndroidDevice.swipe.percent
- `percent` <[float]>
Distance to swipe as a percentage of the widget's size.
### option: AndroidDevice.swipe.speed
- `speed` <[float]>
Optional speed of the swipe in pixels per second.
### option: AndroidDevice.swipe.timeout = %%-android-timeout-%%
## async method: AndroidDevice.tap
Taps on the widget defined by [`param: selector`].
### param: AndroidDevice.tap.selector
- `selector` <[AndroidSelector]>
Selector to tap on.
### option: AndroidDevice.tap.duration
- `duration` <[float]>
Optional duration of the tap in milliseconds.
### option: AndroidDevice.tap.timeout = %%-android-timeout-%%
## async method: AndroidDevice.wait
Waits for the specific [`param: selector`] to either appear or disappear, depending on the [`option: state`].
### param: AndroidDevice.wait.selector
- `selector` <[AndroidSelector]>
Selector to wait for.
### option: AndroidDevice.wait.state
- `state` <"gone">
Optional state. Can be either:
* default - wait for element to be present.
* `'gone'` - wait for element to not be present.
### option: AndroidDevice.wait.timeout = %%-android-timeout-%%
## async method: AndroidDevice.waitForEvent
- returns: <[any]>
Waits for event to fire and passes its value into the predicate function. Returns when the predicate returns truthy value.
### param: AndroidDevice.waitForEvent.event = %%-wait-for-event-event-%%
### param: AndroidDevice.waitForEvent.optionsOrPredicate
- `optionsOrPredicate` <[function]|[Object]>
- `predicate` <[function]> receives the event data and resolves to truthy value when the waiting should resolve.
- `timeout` <[float]> maximum time to wait for in milliseconds. Defaults to `30000` (30 seconds). Pass `0` to
disable timeout. The default value can be changed by using the [`method: AndroidDevice.setDefaultTimeout`].
Either a predicate that receives an event or an options object. Optional.
## async method: AndroidDevice.webView
- returns: <[AndroidWebView]>
This method waits until [AndroidWebView] matching the [`option: selector`] is opened and returns it. If there is already an open [AndroidWebView] matching the [`option: selector`], returns immediately.
### param: AndroidDevice.webView.selector
- `selector` <[Object]>
- `pkg` <[string]> Package identifier.
### option: AndroidDevice.webView.timeout = %%-android-timeout-%%
## method: AndroidDevice.webViews
- returns: <[Array]<[AndroidWebView]>>
Currently open WebViews.

View File

@ -0,0 +1,78 @@
# class: AndroidInput
* langs: js
## async method: AndroidInput.drag
Performs a drag between [`param: from`] and [`param: to`] points.
### param: AndroidInput.drag.from
- `from` <[Object]>
- `x` <[float]>
- `y` <[float]>
The start point of the drag.
### param: AndroidInput.drag.to
- `to` <[Object]>
- `x` <[float]>
- `y` <[float]>
The end point of the drag.
### param: AndroidInput.drag.steps
- `steps` <[int]>
The number of steps in the drag. Each step takes 5 milliseconds to complete.
## async method: AndroidInput.press
Presses the [`param: key`].
### param: AndroidInput.press.key
- `key` <[AndroidKey]>
Key to press.
## async method: AndroidInput.swipe
Swipes following the path defined by [`param: segments`].
### param: AndroidInput.swipe.from
- `from` <[Object]>
- `x` <[float]>
- `y` <[float]>
The point to start swiping from.
### param: AndroidInput.swipe.segments
- `segments` <[Array]<[Object]>>
- `x` <[float]>
- `y` <[float]>
Points following the [`param: from`] point in the swipe gesture.
### param: AndroidInput.swipe.steps
- `steps` <[int]>
The number of steps for each segment. Each step takes 5 milliseconds to complete, so 100 steps means half a second per each segment.
## async method: AndroidInput.tap
Taps at the specified [`param: point`].
### param: AndroidInput.tap.point
- `point` <[Object]>
- `x` <[float]>
- `y` <[float]>
The point to tap at.
## async method: AndroidInput.type
Types [`param: text`] into currently focused widget.
### param: AndroidInput.type.text
- `text` <[string]>
Text to type.

View File

@ -0,0 +1,26 @@
# class: AndroidSocket
* langs: js
[AndroidSocket] is a way to communicate with a process launched on the [AndroidDevice]. Use [`method: AndroidDevice.open`] to open a socket.
## event: AndroidSocket.close
Emitted when the socket is closed.
## event: AndroidSocket.data
- type: <[Buffer]>
Emitted when data is available to read from the socket.
## async method: AndroidSocket.close
Closes the socket.
## async method: AndroidSocket.write
Writes some [`param: data`] to the socket.
### param: AndroidSocket.write.data
- `data` <[Buffer]>
Data to write.

View File

@ -0,0 +1,23 @@
# class: AndroidWebView
* langs: js
[AndroidWebView] represents a WebView open on the [AndroidDevice]. WebView is usually obtained using [`method: AndroidDevice.webView`].
## event: AndroidWebView.close
Emitted when the WebView is closed.
## async method: AndroidWebView.page
- returns: <[Page]>
Connects to the WebView and returns a regular Playwright [Page] to interact with.
## method: AndroidWebView.pid
- returns: <[int]>
WebView process PID.
## method: AndroidWebView.pkg
- returns: <[string]>
WebView package identifier.

View File

@ -498,6 +498,13 @@ Receives the event data and resolves to truthy value when the waiting should res
Maximum time to wait for in milliseconds. Defaults to `30000` (30 seconds). Pass `0` to disable timeout.
The default value can be changed by using the [`method: BrowserContext.setDefaultTimeout`].
## android-timeout
* langs: js
- `timeout` <[float]>
Maximum time in milliseconds, defaults to 30 seconds, pass `0` to disable timeout. The default value can be changed by
using the [`method: AndroidDevice.setDefaultTimeout`] method.
## shared-context-params-list
- %%-context-option-acceptdownloads-%%
- %%-context-option-ignorehttpserrors-%%

View File

@ -8,38 +8,71 @@ title: "Mobile (experimental)"
Mobile support is experimental and uses prefixed provisional API.
:::
You can try Playwright against Chrome for Android today. This support is experimental. Support for devices is tracked in the issue [#1122](https://github.com/microsoft/playwright/issues/1122).
You can try Playwright against Android, Chrome for Android and Android WebView today. This support is experimental. Support for devices is tracked in the issue [#1122](https://github.com/microsoft/playwright/issues/1122).
See [Android] for documentation.
## Requirements
- [ADB daemon](https://developer.android.com/studio/command-line/adb) running and authenticated with your device.
- Android device or AVD Emulator.
- [ADB daemon](https://developer.android.com/studio/command-line/adb) running and authenticated with your device. Typically running `adb devices` is all you need to do.
- [`Chrome 87`](https://play.google.com/store/apps/details?id=com.android.chrome) or newer installed on the device
- "Enable command line on non-rooted devices" enabled in `chrome://flags`.
> Playwright will be looking for ADB daemon on the default port `5037`. It will use the first device available. Typically running `adb devices` is all you need to do.
## How to run
```js
const { _clank } = require('playwright');
const { _android } = require('playwright');
(async () => {
const context = await _clank.launchPersistentContext('', {
viewport: null
});
const [page] = context.pages();
await page.goto('https://webkit.org/');
console.log(await page.evaluate(() => window.location.href));
await page.screenshot({ path: 'example.png' });
await context.close();
// Connect to the device.
const [device] = await playwright._android.devices();
console.log(`Model: ${device.model()}`);
console.log(`Serial: ${device.serial()}`);
// Take screenshot of the whole device.
await device.screenshot({ path: 'device.png' });
{
// --------------------- WebView -----------------------
// Launch an application with WebView.
await device.shell('am force-stop org.chromium.webview_shell');
await device.shell('am start org.chromium.webview_shell/.WebViewBrowserActivity');
// Get the WebView.
const webview = await device.webView({ pkg: 'org.chromium.webview_shell' });
// Fill the input box.
await device.fill({ res: 'org.chromium.webview_shell:id/url_field' }, 'github.com/microsoft/playwright');
await device.press({ res: 'org.chromium.webview_shell:id/url_field' }, 'Enter');
// Work with WebView's page as usual.
const page = await webview.page();
await page.page.waitForNavigation({ url: /.*microsoft\/playwright.*/ });
console.log(await page.title());
}
{
// --------------------- Browser -----------------------
// Launch Chrome browser.
await device.shell('am force-stop com.android.chrome');
const context = await device.launchBrowser();
// Use BrowserContext as usual.
const page = await context.newPage();
await page.goto('https://webkit.org/');
console.log(await page.evaluate(() => window.location.href));
await page.screenshot({ path: 'page.png' });
await context.close();
}
// Close the device.
await device.close();
})();
```
> [Clank](https://chromium.googlesource.com/chromium/src/+/master/docs/memory/android_dev_tips.md) is a code name for Chrome for Android.
## Known limitations
- Raw USB operation is not yet supported, so you need ADB.
- Only `launchPersistentContext` works, launching ephemeral contexts is not supported.
- Passing `viewport: null` is necessary to make sure resolution is not emulated.
- Device needs to be awake to produce screenshots. Enabling "Stay awake" developer mode will help.
- We didn't run all the tests against the device, so not everything works.

1
index.d.ts vendored
View File

@ -21,3 +21,4 @@ export const webkit: types.BrowserType<types.WebKitBrowser>;
export const chromium: types.BrowserType<types.ChromiumBrowser>;
export const firefox: types.BrowserType<types.FirefoxBrowser>;
export const _electron: types.Electron;
export const _android: types.Android;

View File

@ -14,7 +14,6 @@
* limitations under the License.
*/
import { Android } from './types/android';
import { Android } from './types/types';
export * from './types/types';
export * from './types/android';
export const android: Android;

View File

@ -21,7 +21,7 @@ import * as channels from '../protocol/channels';
import { Events } from './events';
import { BrowserContext, prepareBrowserContextOptions } from './browserContext';
import { ChannelOwner } from './channelOwner';
import * as androidApi from '../../types/android';
import * as api from '../../types/types';
import * as types from './types';
import { Page } from './page';
import { TimeoutSettings } from '../utils/timeoutSettings';
@ -29,10 +29,10 @@ import { Waiter } from './waiter';
import { EventEmitter } from 'events';
import { ChromiumBrowserContext } from './chromiumBrowserContext';
type Direction = 'down' | 'up' | 'left' | 'right';
type Direction = 'down' | 'up' | 'left' | 'right';
type SpeedOptions = { speed?: number };
export class Android extends ChannelOwner<channels.AndroidChannel, channels.AndroidInitializer> implements androidApi.Android {
export class Android extends ChannelOwner<channels.AndroidChannel, channels.AndroidInitializer> implements api.Android {
readonly _timeoutSettings: TimeoutSettings;
static from(android: channels.AndroidChannel): Android {
@ -57,7 +57,7 @@ export class Android extends ChannelOwner<channels.AndroidChannel, channels.Andr
}
}
export class AndroidDevice extends ChannelOwner<channels.AndroidDeviceChannel, channels.AndroidDeviceInitializer> implements androidApi.AndroidDevice {
export class AndroidDevice extends ChannelOwner<channels.AndroidDeviceChannel, channels.AndroidDeviceInitializer> implements api.AndroidDevice {
readonly _timeoutSettings: TimeoutSettings;
private _webViews = new Map<number, AndroidWebView>();
@ -65,11 +65,11 @@ export class AndroidDevice extends ChannelOwner<channels.AndroidDeviceChannel, c
return (androidDevice as any)._object;
}
input: Input;
input: AndroidInput;
constructor(parent: ChannelOwner, type: string, guid: string, initializer: channels.AndroidDeviceInitializer) {
super(parent, type, guid, initializer);
this.input = new Input(this);
this.input = new AndroidInput(this);
this._timeoutSettings = new TimeoutSettings((parent as Android)._timeoutSettings);
this._channel.on('webViewAdded', ({ webView }) => this._onWebViewAdded(webView));
this._channel.on('webViewRemoved', ({ pid }) => this._onWebViewRemoved(pid));
@ -115,83 +115,77 @@ export class AndroidDevice extends ChannelOwner<channels.AndroidDeviceChannel, c
});
}
async wait(selector: androidApi.AndroidSelector, options?: { state?: 'gone' } & types.TimeoutOptions) {
async wait(selector: api.AndroidSelector, options?: { state?: 'gone' } & types.TimeoutOptions) {
await this._wrapApiCall('androidDevice.wait', async () => {
await this._channel.wait({ selector: toSelectorChannel(selector), ...options });
});
}
async fill(selector: androidApi.AndroidSelector, text: string, options?: types.TimeoutOptions) {
async fill(selector: api.AndroidSelector, text: string, options?: types.TimeoutOptions) {
await this._wrapApiCall('androidDevice.fill', async () => {
await this._channel.fill({ selector: toSelectorChannel(selector), text, ...options });
});
}
async press(selector: androidApi.AndroidSelector, key: androidApi.AndroidKey, options?: types.TimeoutOptions) {
async press(selector: api.AndroidSelector, key: api.AndroidKey, options?: types.TimeoutOptions) {
await this.tap(selector, options);
await this.input.press(key);
}
async tap(selector: androidApi.AndroidSelector, options?: { duration?: number } & types.TimeoutOptions) {
async tap(selector: api.AndroidSelector, options?: { duration?: number } & types.TimeoutOptions) {
await this._wrapApiCall('androidDevice.tap', async () => {
await this._channel.tap({ selector: toSelectorChannel(selector), ...options });
});
}
async drag(selector: androidApi.AndroidSelector, dest: types.Point, options?: SpeedOptions & types.TimeoutOptions) {
async drag(selector: api.AndroidSelector, dest: types.Point, options?: SpeedOptions & types.TimeoutOptions) {
await this._wrapApiCall('androidDevice.drag', async () => {
await this._channel.drag({ selector: toSelectorChannel(selector), dest, ...options });
});
}
async fling(selector: androidApi.AndroidSelector, direction: Direction, options?: SpeedOptions & types.TimeoutOptions) {
async fling(selector: api.AndroidSelector, direction: Direction, options?: SpeedOptions & types.TimeoutOptions) {
await this._wrapApiCall('androidDevice.fling', async () => {
await this._channel.fling({ selector: toSelectorChannel(selector), direction, ...options });
});
}
async longTap(selector: androidApi.AndroidSelector, options?: types.TimeoutOptions) {
async longTap(selector: api.AndroidSelector, options?: types.TimeoutOptions) {
await this._wrapApiCall('androidDevice.longTap', async () => {
await this._channel.longTap({ selector: toSelectorChannel(selector), ...options });
});
}
async pinchClose(selector: androidApi.AndroidSelector, percent: number, options?: SpeedOptions & types.TimeoutOptions) {
async pinchClose(selector: api.AndroidSelector, percent: number, options?: SpeedOptions & types.TimeoutOptions) {
await this._wrapApiCall('androidDevice.pinchClose', async () => {
await this._channel.pinchClose({ selector: toSelectorChannel(selector), percent, ...options });
});
}
async pinchOpen(selector: androidApi.AndroidSelector, percent: number, options?: SpeedOptions & types.TimeoutOptions) {
async pinchOpen(selector: api.AndroidSelector, percent: number, options?: SpeedOptions & types.TimeoutOptions) {
await this._wrapApiCall('androidDevice.pinchOpen', async () => {
await this._channel.pinchOpen({ selector: toSelectorChannel(selector), percent, ...options });
});
}
async scroll(selector: androidApi.AndroidSelector, direction: Direction, percent: number, options?: SpeedOptions & types.TimeoutOptions) {
async scroll(selector: api.AndroidSelector, direction: Direction, percent: number, options?: SpeedOptions & types.TimeoutOptions) {
await this._wrapApiCall('androidDevice.scroll', async () => {
await this._channel.scroll({ selector: toSelectorChannel(selector), direction, percent, ...options });
});
}
async swipe(selector: androidApi.AndroidSelector, direction: Direction, percent: number, options?: SpeedOptions & types.TimeoutOptions) {
async swipe(selector: api.AndroidSelector, direction: Direction, percent: number, options?: SpeedOptions & types.TimeoutOptions) {
await this._wrapApiCall('androidDevice.swipe', async () => {
await this._channel.swipe({ selector: toSelectorChannel(selector), direction, percent, ...options });
});
}
async info(selector: androidApi.AndroidSelector): Promise<androidApi.AndroidElementInfo> {
async info(selector: api.AndroidSelector): Promise<api.AndroidElementInfo> {
return await this._wrapApiCall('androidDevice.info', async () => {
return (await this._channel.info({ selector: toSelectorChannel(selector) })).info;
});
}
async tree(): Promise<androidApi.AndroidElementInfo> {
return await this._wrapApiCall('androidDevice.tree', async () => {
return (await this._channel.tree()).tree;
});
}
async screenshot(options: { path?: string } = {}): Promise<Buffer> {
return await this._wrapApiCall('androidDevice.screenshot', async () => {
const { binary } = await this._channel.screenshot();
@ -255,7 +249,7 @@ export class AndroidDevice extends ChannelOwner<channels.AndroidDeviceChannel, c
}
}
export class AndroidSocket extends ChannelOwner<channels.AndroidSocketChannel, channels.AndroidSocketInitializer> implements androidApi.AndroidSocket {
export class AndroidSocket extends ChannelOwner<channels.AndroidSocketChannel, channels.AndroidSocketInitializer> implements api.AndroidSocket {
static from(androidDevice: channels.AndroidSocketChannel): AndroidSocket {
return (androidDevice as any)._object;
}
@ -285,7 +279,7 @@ async function loadFile(file: string | Buffer): Promise<string> {
return file.toString('base64');
}
class Input implements androidApi.AndroidInput {
export class AndroidInput implements api.AndroidInput {
private _device: AndroidDevice;
constructor(device: AndroidDevice) {
@ -298,7 +292,7 @@ class Input implements androidApi.AndroidInput {
});
}
async press(key: androidApi.AndroidKey) {
async press(key: api.AndroidKey) {
return this._device._wrapApiCall('androidDevice.inputPress', async () => {
await this._device._channel.inputPress({ key });
});
@ -323,7 +317,7 @@ class Input implements androidApi.AndroidInput {
}
}
function toSelectorChannel(selector: androidApi.AndroidSelector): channels.AndroidSelector {
function toSelectorChannel(selector: api.AndroidSelector): channels.AndroidSelector {
const {
checkable,
checked,
@ -373,7 +367,7 @@ function toSelectorChannel(selector: androidApi.AndroidSelector): channels.Andro
};
}
export class AndroidWebView extends EventEmitter implements androidApi.AndroidWebView {
export class AndroidWebView extends EventEmitter implements api.AndroidWebView {
private _device: AndroidDevice;
private _data: channels.AndroidWebView;
private _pagePromise: Promise<Page> | undefined;

View File

@ -15,6 +15,7 @@
*/
export { Accessibility } from './accessibility';
export { Android, AndroidDevice, AndroidWebView, AndroidInput, AndroidSocket } from './android';
export { Browser } from './browser';
export { BrowserContext } from './browserContext';
export { BrowserServer } from './browserType';

View File

@ -100,10 +100,6 @@ export class AndroidDeviceDispatcher extends Dispatcher<AndroidDevice, channels.
return { info: await this._object.send('info', params) };
}
async tree(params: channels.AndroidDeviceTreeParams): Promise<channels.AndroidDeviceTreeResult> {
return { tree: await this._object.send('tree', params) };
}
async inputType(params: channels.AndroidDeviceInputTypeParams) {
const text = params.text;
const keyCodes: number[] = [];

View File

@ -2604,7 +2604,6 @@ export interface AndroidDeviceChannel extends Channel {
scroll(params: AndroidDeviceScrollParams, metadata?: Metadata): Promise<AndroidDeviceScrollResult>;
swipe(params: AndroidDeviceSwipeParams, metadata?: Metadata): Promise<AndroidDeviceSwipeResult>;
info(params: AndroidDeviceInfoParams, metadata?: Metadata): Promise<AndroidDeviceInfoResult>;
tree(params?: AndroidDeviceTreeParams, metadata?: Metadata): Promise<AndroidDeviceTreeResult>;
screenshot(params?: AndroidDeviceScreenshotParams, metadata?: Metadata): Promise<AndroidDeviceScreenshotResult>;
inputType(params: AndroidDeviceInputTypeParams, metadata?: Metadata): Promise<AndroidDeviceInputTypeResult>;
inputPress(params: AndroidDeviceInputPressParams, metadata?: Metadata): Promise<AndroidDeviceInputPressResult>;
@ -2740,11 +2739,6 @@ export type AndroidDeviceInfoOptions = {
export type AndroidDeviceInfoResult = {
info: AndroidElementInfo,
};
export type AndroidDeviceTreeParams = {};
export type AndroidDeviceTreeOptions = {};
export type AndroidDeviceTreeResult = {
tree: AndroidElementInfo,
};
export type AndroidDeviceScreenshotParams = {};
export type AndroidDeviceScreenshotOptions = {};
export type AndroidDeviceScreenshotResult = {

View File

@ -2291,10 +2291,6 @@ AndroidDevice:
returns:
info: AndroidElementInfo
tree:
returns:
tree: AndroidElementInfo
screenshot:
returns:
binary: binary

View File

@ -1010,7 +1010,6 @@ export function createScheme(tChannel: (name: string) => Validator): Scheme {
scheme.AndroidDeviceInfoParams = tObject({
selector: tType('AndroidSelector'),
});
scheme.AndroidDeviceTreeParams = tOptional(tObject({}));
scheme.AndroidDeviceScreenshotParams = tOptional(tObject({}));
scheme.AndroidDeviceInputTypeParams = tObject({
text: tString,

View File

@ -14,7 +14,7 @@
* limitations under the License.
*/
import type { Android, AndroidDevice } from '../../types/android';
import type { Android, AndroidDevice } from '../..';
import { folio as baseFolio } from '../fixtures';
const fixtures = baseFolio.extend<{

View File

@ -19,7 +19,7 @@ const {installCoverageHooks} = require('./coverage');
const browserName = process.env.BROWSER || 'chromium';
const api = new Set(installCoverageHooks(browserName).coverage.keys());
let api = new Set(installCoverageHooks(browserName).coverage.keys());
// coverage exceptions
@ -38,8 +38,8 @@ if (browserName !== 'chromium') {
if (browserName === 'webkit')
api.delete('browserContext.clearPermissions');
// Screencast APIs that are not publicly available.
api.delete('browserContext.emit("screencaststarted")');
// Android coverage is abysmal.
api = new Set(Array.from(api).filter(name => !name.toLowerCase().startsWith('android')));
const coverageDir = path.join(__dirname, 'coverage-report');

View File

@ -29,7 +29,7 @@ function traceAPICoverage(apiCoverage, api, events) {
const method = Reflect.get(classType.prototype, methodName);
if (methodName === 'constructor' || typeof methodName !== 'string' || methodName.startsWith('_') || typeof method !== 'function')
continue;
apiCoverage.set(`${className}.${methodName}`, false);
const override = function(...args) {
apiCoverage.set(`${className}.${methodName}`, true);

View File

@ -27,7 +27,6 @@ import { installCoverageHooks } from './coverage';
import { folio as httpFolio } from './http.fixtures';
import { folio as playwrightFolio } from './playwright.fixtures';
import { PlaywrightClient } from '../lib/remote/playwrightClient';
import type { Android } from '../types/android';
export { expect, config } from 'folio';
const removeFolderAsync = util.promisify(require('rimraf'));
@ -190,8 +189,3 @@ export const beforeEach = folio.beforeEach;
export const afterEach = folio.afterEach;
export const beforeAll = folio.beforeAll;
export const afterAll = folio.afterAll;
declare module '../index' {
const _android: Android;
}

161
types/android.d.ts vendored
View File

@ -1,161 +0,0 @@
/**
* Copyright (c) Microsoft Corporation.
*
* 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 { EventEmitter } from 'events';
import { BrowserContextOptions, Page, ChromiumBrowserContext } from './types';
export interface Android extends EventEmitter {
setDefaultTimeout(timeout: number): void;
devices(): Promise<AndroidDevice[]>;
}
export interface AndroidDevice extends EventEmitter {
input: AndroidInput;
setDefaultTimeout(timeout: number): void;
on(event: 'webview', handler: (webView: AndroidWebView) => void): this;
waitForEvent(event: string, optionsOrPredicate?: (data: any) => boolean | { timeout?: number, predicate?: (data: any) => boolean }): Promise<any>;
serial(): string;
model(): string;
webViews(): AndroidWebView[];
webView(selector: { pkg: string }, options?: { timeout?: number }): Promise<AndroidWebView>;
shell(command: string): Promise<Buffer>;
open(command: string): Promise<AndroidSocket>;
installApk(file: string | Buffer, options?: { args?: string[] }): Promise<void>;
push(file: string | Buffer, path: string, options?: { mode?: number }): Promise<void>;
launchBrowser(options?: BrowserContextOptions & { pkg?: string }): Promise<ChromiumBrowserContext>;
close(): Promise<void>;
wait(selector: AndroidSelector, options?: { state?: 'gone' } & { timeout?: number }): Promise<void>;
fill(selector: AndroidSelector, text: string, options?: { timeout?: number }): Promise<void>;
press(selector: AndroidSelector, key: AndroidKey, options?: { duration?: number } & { timeout?: number }): Promise<void>;
tap(selector: AndroidSelector, options?: { duration?: number } & { timeout?: number }): Promise<void>;
drag(selector: AndroidSelector, dest: { x: number, y: number }, options?: { speed?: number } & { timeout?: number }): Promise<void>;
fling(selector: AndroidSelector, direction: 'down' | 'up' | 'left' | 'right', options?: { speed?: number } & { timeout?: number }): Promise<void>;
longTap(selector: AndroidSelector, options?: { timeout?: number }): Promise<void>;
pinchClose(selector: AndroidSelector, percent: number, options?: { speed?: number } & { timeout?: number }): Promise<void>;
pinchOpen(selector: AndroidSelector, percent: number, options?: { speed?: number } & { timeout?: number }): Promise<void>;
scroll(selector: AndroidSelector, direction: 'down' | 'up' | 'left' | 'right', percent: number, options?: { speed?: number } & { timeout?: number }): Promise<void>;
swipe(selector: AndroidSelector, direction: 'down' | 'up' | 'left' | 'right', percent: number, options?: { speed?: number } & { timeout?: number }): Promise<void>;
info(selector: AndroidSelector): Promise<AndroidElementInfo>;
screenshot(options?: { path?: string }): Promise<Buffer>;
}
export interface AndroidSocket extends EventEmitter {
on(event: 'data', handler: (data: Buffer) => void): this;
on(event: 'close', handler: () => void): this;
write(data: Buffer): Promise<void>;
close(): Promise<void>;
}
export interface AndroidInput {
type(text: string): Promise<void>;
press(key: AndroidKey): Promise<void>;
tap(point: { x: number, y: number }): Promise<void>;
swipe(from: { x: number, y: number }, segments: { x: number, y: number }[], steps: number): Promise<void>;
drag(from: { x: number, y: number }, to: { x: number, y: number }, steps: number): Promise<void>;
}
export interface AndroidWebView extends EventEmitter {
on(event: 'close', handler: () => void): this;
pid(): number;
pkg(): string;
page(): Promise<Page>;
}
export type AndroidElementInfo = {
clazz: string;
desc: string;
res: string;
pkg: string;
text: string;
bounds: { x: number, y: number, width: number, height: number };
checkable: boolean;
checked: boolean;
clickable: boolean;
enabled: boolean;
focusable: boolean;
focused: boolean;
longClickable: boolean;
scrollable: boolean;
selected: boolean;
};
export type AndroidSelector = {
checkable?: boolean,
checked?: boolean,
clazz?: string | RegExp,
clickable?: boolean,
depth?: number,
desc?: string | RegExp,
enabled?: boolean,
focusable?: boolean,
focused?: boolean,
hasChild?: { selector: AndroidSelector },
hasDescendant?: { selector: AndroidSelector, maxDepth?: number },
longClickable?: boolean,
pkg?: string | RegExp,
res?: string | RegExp,
scrollable?: boolean,
selected?: boolean,
text?: string | RegExp,
};
export type AndroidKey =
'Unknown' |
'SoftLeft' | 'SoftRight' |
'Home' |
'Back' |
'Call' | 'EndCall' |
'0' | '1' | '2' | '3' | '4' | '5' | '6' | '7' | '8' | '9' |
'Star' | 'Pound' | '*' | '#' |
'DialUp' | 'DialDown' | 'DialLeft' | 'DialRight' | 'DialCenter' |
'VolumeUp' | 'VolumeDown' |
'Power' |
'Camera' |
'Clear' |
'A' | 'B' | 'C' | 'D' | 'E' | 'F' | 'G' | 'H' | 'I' | 'J' | 'K' | 'L' | 'M' |
'N' | 'O' | 'P' | 'Q' | 'R' | 'S' | 'T' | 'U' | 'V' | 'W' | 'X' | 'Y' | 'Z' |
'Comma' | ',' |
'Period' | '.' |
'AltLeft' | 'AltRight' |
'ShiftLeft' | 'ShiftRight' |
'Tab' | '\t' |
'Space' | ' ' |
'Sym' |
'Explorer' |
'Envelop' |
'Enter' | '\n' |
'Del' |
'Grave' |
'Minus' | '-' |
'Equals' | '=' |
'LeftBracket' | '(' |
'RightBracket' | ')' |
'Backslash' | '\\' |
'Semicolon' | ';' |
'Apostrophe' | '`' |
'Slash' | '/' |
'At' |
'Num' |
'HeadsetHook' |
'Focus' |
'Plus' | '+' |
'Menu' |
'Notification' |
'Search';

899
types/types.d.ts vendored
View File

@ -7217,10 +7217,909 @@ export interface ElectronApplication {
*/
windows(): Array<Page>;}
export type AndroidElementInfo = {
clazz: string;
desc: string;
res: string;
pkg: string;
text: string;
bounds: { x: number, y: number, width: number, height: number };
checkable: boolean;
checked: boolean;
clickable: boolean;
enabled: boolean;
focusable: boolean;
focused: boolean;
longClickable: boolean;
scrollable: boolean;
selected: boolean;
};
export type AndroidSelector = {
checkable?: boolean,
checked?: boolean,
clazz?: string | RegExp,
clickable?: boolean,
depth?: number,
desc?: string | RegExp,
enabled?: boolean,
focusable?: boolean,
focused?: boolean,
hasChild?: { selector: AndroidSelector },
hasDescendant?: { selector: AndroidSelector, maxDepth?: number },
longClickable?: boolean,
pkg?: string | RegExp,
res?: string | RegExp,
scrollable?: boolean,
selected?: boolean,
text?: string | RegExp,
};
export type AndroidKey =
'Unknown' |
'SoftLeft' | 'SoftRight' |
'Home' |
'Back' |
'Call' | 'EndCall' |
'0' | '1' | '2' | '3' | '4' | '5' | '6' | '7' | '8' | '9' |
'Star' | 'Pound' | '*' | '#' |
'DialUp' | 'DialDown' | 'DialLeft' | 'DialRight' | 'DialCenter' |
'VolumeUp' | 'VolumeDown' |
'Power' |
'Camera' |
'Clear' |
'A' | 'B' | 'C' | 'D' | 'E' | 'F' | 'G' | 'H' | 'I' | 'J' | 'K' | 'L' | 'M' |
'N' | 'O' | 'P' | 'Q' | 'R' | 'S' | 'T' | 'U' | 'V' | 'W' | 'X' | 'Y' | 'Z' |
'Comma' | ',' |
'Period' | '.' |
'AltLeft' | 'AltRight' |
'ShiftLeft' | 'ShiftRight' |
'Tab' | '\t' |
'Space' | ' ' |
'Sym' |
'Explorer' |
'Envelop' |
'Enter' | '\n' |
'Del' |
'Grave' |
'Minus' | '-' |
'Equals' | '=' |
'LeftBracket' | '(' |
'RightBracket' | ')' |
'Backslash' | '\\' |
'Semicolon' | ';' |
'Apostrophe' | '`' |
'Slash' | '/' |
'At' |
'Num' |
'HeadsetHook' |
'Focus' |
'Plus' | '+' |
'Menu' |
'Notification' |
'Search';
// This is required to not export everything by default. See https://github.com/Microsoft/TypeScript/issues/19545#issuecomment-340490459
export {};
/**
* Playwright has **experimental** support for Android automation. You can access android namespace via:
*
* ```js
* const { _android } = require('playwright');
* ```
*
* An example of the Android automation script would be:
*
* ```js
* const { _android } = require('playwright');
*
* (async () => {
* // Connect to the device.
* const [device] = await playwright._android.devices();
* console.log(`Model: ${device.model()}`);
* console.log(`Serial: ${device.serial()}`);
* // Take screenshot of the whole device.
* await device.screenshot({ path: 'device.png' });
*
* {
* // --------------------- WebView -----------------------
*
* // Launch an application with WebView.
* await device.shell('am force-stop org.chromium.webview_shell');
* await device.shell('am start org.chromium.webview_shell/.WebViewBrowserActivity');
* // Get the WebView.
* const webview = await device.webView({ pkg: 'org.chromium.webview_shell' });
*
* // Fill the input box.
* await device.fill({ res: 'org.chromium.webview_shell:id/url_field' }, 'github.com/microsoft/playwright');
* await device.press({ res: 'org.chromium.webview_shell:id/url_field' }, 'Enter');
*
* // Work with WebView's page as usual.
* const page = await webview.page();
* await page.page.waitForNavigation({ url: /.*microsoft\/playwright.*\/ });
* console.log(await page.title());
* }
*
* {
* // --------------------- Browser -----------------------
*
* // Launch Chrome browser.
* await device.shell('am force-stop com.android.chrome');
* const context = await device.launchBrowser();
*
* // Use BrowserContext as usual.
* const page = await context.newPage();
* await page.goto('https://webkit.org/');
* console.log(await page.evaluate(() => window.location.href));
* await page.screenshot({ path: 'page.png' });
*
* await context.close();
* }
*
* // Close the device.
* await device.close();
* })();
* ```
*
* Note that since you don't need Playwright to install web browsers when testing Android, you can omit browser download
* via setting the following environment variable when installing Playwright:
*
* ```sh js
* $ PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD=1 npm i -D playwright
* ```
*
*/
export interface Android {
/**
* Returns the list of detected Android devices.
*/
devices(): Promise<Array<AndroidDevice>>;
/**
* This setting will change the default maximum time for all the methods accepting `timeout` option.
* @param timeout Maximum time in milliseconds
*/
setDefaultTimeout(timeout: number): void;
}
/**
* [AndroidDevice] represents a connected device, either real hardware or emulated. Devices can be obtained using
* [android.devices()](https://playwright.dev/docs/api/class-android#androiddevices).
*/
export interface AndroidDevice {
/**
* Emitted when a new WebView instance is detected.
*/
on(event: 'webview', listener: (androidWebView: AndroidWebView) => void): this;
/**
* Emitted when a new WebView instance is detected.
*/
once(event: 'webview', listener: (androidWebView: AndroidWebView) => void): this;
/**
* Emitted when a new WebView instance is detected.
*/
addListener(event: 'webview', listener: (androidWebView: AndroidWebView) => void): this;
/**
* Emitted when a new WebView instance is detected.
*/
removeListener(event: 'webview', listener: (androidWebView: AndroidWebView) => void): this;
/**
* Emitted when a new WebView instance is detected.
*/
off(event: 'webview', listener: (androidWebView: AndroidWebView) => void): this;
/**
* Disconnects from the device.
*/
close(): Promise<void>;
/**
* Drags the widget defined by `selector` towards `dest` point.
* @param selector Selector to drag.
* @param dest Point to drag to.
* @param options
*/
drag(selector: AndroidSelector, dest: {
x: number;
y: number;
}, options?: {
/**
* Optional speed of the drag in pixels per second.
*/
speed?: number;
/**
* Maximum time in milliseconds, defaults to 30 seconds, pass `0` to disable timeout. The default value can be changed by
* using the
* [androidDevice.setDefaultTimeout(timeout)](https://playwright.dev/docs/api/class-androiddevice#androiddevicesetdefaulttimeouttimeout)
* method.
*/
timeout?: number;
}): Promise<void>;
/**
* Fills the specific `selector` input box with `text`.
* @param selector Selector to fill.
* @param text Text to be filled in the input box.
* @param options
*/
fill(selector: AndroidSelector, text: string, options?: {
/**
* Maximum time in milliseconds, defaults to 30 seconds, pass `0` to disable timeout. The default value can be changed by
* using the
* [androidDevice.setDefaultTimeout(timeout)](https://playwright.dev/docs/api/class-androiddevice#androiddevicesetdefaulttimeouttimeout)
* method.
*/
timeout?: number;
}): Promise<void>;
/**
* Flings the widget defined by `selector` in the specified `direction`.
* @param selector Selector to fling.
* @param direction Fling direction.
* @param options
*/
fling(selector: AndroidSelector, direction: "down"|"up"|"left"|"right", options?: {
/**
* Optional speed of the fling in pixels per second.
*/
speed?: number;
/**
* Maximum time in milliseconds, defaults to 30 seconds, pass `0` to disable timeout. The default value can be changed by
* using the
* [androidDevice.setDefaultTimeout(timeout)](https://playwright.dev/docs/api/class-androiddevice#androiddevicesetdefaulttimeouttimeout)
* method.
*/
timeout?: number;
}): Promise<void>;
/**
* Returns information about a widget defined by `selector`.
* @param selector Selector to return information about.
*/
info(selector: AndroidSelector): Promise<AndroidElementInfo>;
input: AndroidInput;
/**
* Installs an apk on the device.
* @param file Either a path to the apk file, or apk file content.
* @param options
*/
installApk(file: string|Buffer, options?: {
/**
* Optional arguments to pass to the `shell:cmd package install` call. Defaults to `-r -t -S`.
*/
args?: Array<string>;
}): Promise<void>;
/**
* Launches Chrome browser on the device, and returns its persistent context.
* @param options
*/
launchBrowser(options?: {
/**
* Whether to automatically download all the attachments. Defaults to `false` where all the downloads are canceled.
*/
acceptDownloads?: boolean;
/**
* Toggles bypassing page's Content-Security-Policy.
*/
bypassCSP?: boolean;
/**
* Emulates `'prefers-colors-scheme'` media feature, supported values are `'light'`, `'dark'`, `'no-preference'`. See
* [page.emulateMedia([options])](https://playwright.dev/docs/api/class-page#pageemulatemediaoptions) for more details.
* Defaults to '`light`'.
*/
colorScheme?: "light"|"dark"|"no-preference";
/**
* Optional package name to launch instead of default Chrome for Android.
*/
command?: string;
/**
* Specify device scale factor (can be thought of as dpr). Defaults to `1`.
*/
deviceScaleFactor?: number;
/**
* An object containing additional HTTP headers to be sent with every request. All header values must be strings.
*/
extraHTTPHeaders?: { [key: string]: string; };
geolocation?: {
/**
* Latitude between -90 and 90.
*/
latitude: number;
/**
* Longitude between -180 and 180.
*/
longitude: number;
/**
* Non-negative accuracy value. Defaults to `0`.
*/
accuracy?: number;
};
/**
* Specifies if viewport supports touch events. Defaults to false.
*/
hasTouch?: boolean;
/**
* Credentials for [HTTP authentication](https://developer.mozilla.org/en-US/docs/Web/HTTP/Authentication).
*/
httpCredentials?: {
username: string;
password: string;
};
/**
* Whether to ignore HTTPS errors during navigation. Defaults to `false`.
*/
ignoreHTTPSErrors?: boolean;
/**
* Whether the `meta viewport` tag is taken into account and touch events are enabled. Defaults to `false`. Not supported
* in Firefox.
*/
isMobile?: boolean;
/**
* Whether or not to enable JavaScript in the context. Defaults to `true`.
*/
javaScriptEnabled?: boolean;
/**
* Specify user locale, for example `en-GB`, `de-DE`, etc. Locale will affect `navigator.language` value, `Accept-Language`
* request header value as well as number and date formatting rules.
*/
locale?: string;
/**
* Logger sink for Playwright logging.
*/
logger?: Logger;
/**
* Whether to emulate network being offline. Defaults to `false`.
*/
offline?: boolean;
/**
* A list of permissions to grant to all pages in this context. See
* [browserContext.grantPermissions(permissions[, options])](https://playwright.dev/docs/api/class-browsercontext#browsercontextgrantpermissionspermissions-options)
* for more details.
*/
permissions?: Array<string>;
/**
* Enables [HAR](http://www.softwareishard.com/blog/har-12-spec) recording for all pages into `recordHar.path` file. If not
* specified, the HAR is not recorded. Make sure to await
* [browserContext.close()](https://playwright.dev/docs/api/class-browsercontext#browsercontextclose) for the HAR to be
* saved.
*/
recordHar?: {
/**
* Optional setting to control whether to omit request content from the HAR. Defaults to `false`.
*/
omitContent?: boolean;
/**
* Path on the filesystem to write the HAR file to.
*/
path: string;
};
/**
* Enables video recording for all pages into `recordVideo.dir` directory. If not specified videos are not recorded. Make
* sure to await [browserContext.close()](https://playwright.dev/docs/api/class-browsercontext#browsercontextclose) for
* videos to be saved.
*/
recordVideo?: {
/**
* Path to the directory to put videos into.
*/
dir: string;
/**
* Optional dimensions of the recorded videos. If not specified the size will be equal to `viewport` scaled down to fit
* into 800x800. If `viewport` is not configured explicitly the video size defaults to 800x450. Actual picture of each page
* will be scaled down if necessary to fit the specified size.
*/
size?: {
/**
* Video frame width.
*/
width: number;
/**
* Video frame height.
*/
height: number;
};
};
/**
* Changes the timezone of the context. See
* [ICU's metaZones.txt](https://cs.chromium.org/chromium/src/third_party/icu/source/data/misc/metaZones.txt?rcl=faee8bc70570192d82d2978a71e2a615788597d1)
* for a list of supported timezone IDs.
*/
timezoneId?: string;
/**
* Specific user agent to use in this context.
*/
userAgent?: string;
/**
* **DEPRECATED** Use `recordVideo` instead.
* @deprecated
*/
videoSize?: {
/**
* Video frame width.
*/
width: number;
/**
* Video frame height.
*/
height: number;
};
/**
* **DEPRECATED** Use `recordVideo` instead.
* @deprecated
*/
videosPath?: string;
/**
* Sets a consistent viewport for each page. Defaults to an 1280x720 viewport. `null` disables the default viewport.
*/
viewport?: null|{
/**
* page width in pixels.
*/
width: number;
/**
* page height in pixels.
*/
height: number;
};
}): Promise<ChromiumBrowserContext>;
/**
* Performs a long tap on the widget defined by `selector`.
* @param selector Selector to tap on.
* @param options
*/
longTap(selector: AndroidSelector, options?: {
/**
* Maximum time in milliseconds, defaults to 30 seconds, pass `0` to disable timeout. The default value can be changed by
* using the
* [androidDevice.setDefaultTimeout(timeout)](https://playwright.dev/docs/api/class-androiddevice#androiddevicesetdefaulttimeouttimeout)
* method.
*/
timeout?: number;
}): Promise<void>;
/**
* Device model.
*/
model(): string;
/**
* Launches a process in the shell on the device and returns a socket to communicate with the launched process.
* @param command
*/
open(command: string): Promise<AndroidSocket>;
/**
* Pinches the widget defined by `selector` in the closing direction.
* @param selector Selector to pinch close.
* @param percent The size of the pinch as a percentage of the widget's size.
* @param options
*/
pinchClose(selector: AndroidSelector, percent: number, options?: {
/**
* Optional speed of the pinch in pixels per second.
*/
speed?: number;
/**
* Maximum time in milliseconds, defaults to 30 seconds, pass `0` to disable timeout. The default value can be changed by
* using the
* [androidDevice.setDefaultTimeout(timeout)](https://playwright.dev/docs/api/class-androiddevice#androiddevicesetdefaulttimeouttimeout)
* method.
*/
timeout?: number;
}): Promise<void>;
/**
* Pinches the widget defined by `selector` in the open direction.
* @param selector Selector to pinch open.
* @param percent The size of the pinch as a percentage of the widget's size.
* @param options
*/
pinchOpen(selector: AndroidSelector, percent: number, options?: {
/**
* Optional speed of the pinch in pixels per second.
*/
speed?: number;
/**
* Maximum time in milliseconds, defaults to 30 seconds, pass `0` to disable timeout. The default value can be changed by
* using the
* [androidDevice.setDefaultTimeout(timeout)](https://playwright.dev/docs/api/class-androiddevice#androiddevicesetdefaulttimeouttimeout)
* method.
*/
timeout?: number;
}): Promise<void>;
/**
* Presses the specific `key` in the widget defined by `selector`.
* @param selector Selector to press the key in.
* @param key The key to press.
* @param options
*/
press(selector: AndroidSelector, key: AndroidKey, options?: {
/**
* Maximum time in milliseconds, defaults to 30 seconds, pass `0` to disable timeout. The default value can be changed by
* using the
* [androidDevice.setDefaultTimeout(timeout)](https://playwright.dev/docs/api/class-androiddevice#androiddevicesetdefaulttimeouttimeout)
* method.
*/
timeout?: number;
}): Promise<void>;
/**
* Copies a file to the device.
* @param file Either a path to the file, or file content.
* @param path Path to the file on the device.
* @param options
*/
push(file: string|Buffer, path: string, options?: {
/**
* Optional file mode, defaults to `644` (`rw-r--r--`).
*/
mode?: number;
}): Promise<void>;
/**
* Returns the buffer with the captured screenshot of the device.
* @param options
*/
screenshot(options?: {
/**
* The file path to save the image to. If `path` is a relative path, then it is resolved relative to the current working
* directory. If no path is provided, the image won't be saved to the disk.
*/
path?: string;
}): Promise<Buffer>;
/**
* Scrolls the widget defined by `selector` in the specified `direction`.
* @param selector Selector to scroll.
* @param direction Scroll direction.
* @param percent Distance to scroll as a percentage of the widget's size.
* @param options
*/
scroll(selector: AndroidSelector, direction: "down"|"up"|"left"|"right", percent: number, options?: {
/**
* Optional speed of the scroll in pixels per second.
*/
speed?: number;
/**
* Maximum time in milliseconds, defaults to 30 seconds, pass `0` to disable timeout. The default value can be changed by
* using the
* [androidDevice.setDefaultTimeout(timeout)](https://playwright.dev/docs/api/class-androiddevice#androiddevicesetdefaulttimeouttimeout)
* method.
*/
timeout?: number;
}): Promise<void>;
/**
* Device serial number.
*/
serial(): string;
/**
* This setting will change the default maximum time for all the methods accepting `timeout` option.
* @param timeout Maximum time in milliseconds
*/
setDefaultTimeout(timeout: number): void;
/**
* Executes a shell command on the device and returns its output.
* @param command Shell command to execute.
*/
shell(command: string): Promise<Buffer>;
/**
* Swipes the widget defined by `selector` in the specified `direction`.
* @param selector Selector to swipe.
* @param direction Swipe direction.
* @param percent Distance to swipe as a percentage of the widget's size.
* @param options
*/
swipe(selector: AndroidSelector, direction: "down"|"up"|"left"|"right", percent: number, options?: {
/**
* Optional speed of the swipe in pixels per second.
*/
speed?: number;
/**
* Maximum time in milliseconds, defaults to 30 seconds, pass `0` to disable timeout. The default value can be changed by
* using the
* [androidDevice.setDefaultTimeout(timeout)](https://playwright.dev/docs/api/class-androiddevice#androiddevicesetdefaulttimeouttimeout)
* method.
*/
timeout?: number;
}): Promise<void>;
/**
* Taps on the widget defined by `selector`.
* @param selector Selector to tap on.
* @param options
*/
tap(selector: AndroidSelector, options?: {
/**
* Optional duration of the tap in milliseconds.
*/
duration?: number;
/**
* Maximum time in milliseconds, defaults to 30 seconds, pass `0` to disable timeout. The default value can be changed by
* using the
* [androidDevice.setDefaultTimeout(timeout)](https://playwright.dev/docs/api/class-androiddevice#androiddevicesetdefaulttimeouttimeout)
* method.
*/
timeout?: number;
}): Promise<void>;
/**
* Waits for the specific `selector` to either appear or disappear, depending on the `state`.
* @param selector Selector to wait for.
* @param options
*/
wait(selector: AndroidSelector, options?: {
/**
* Optional state. Can be either:
* - default - wait for element to be present.
* - `'gone'` - wait for element to not be present.
*/
state?: "gone";
/**
* Maximum time in milliseconds, defaults to 30 seconds, pass `0` to disable timeout. The default value can be changed by
* using the
* [androidDevice.setDefaultTimeout(timeout)](https://playwright.dev/docs/api/class-androiddevice#androiddevicesetdefaulttimeouttimeout)
* method.
*/
timeout?: number;
}): Promise<void>;
/**
* Emitted when a new WebView instance is detected.
*/
waitForEvent(event: 'webview', optionsOrPredicate?: { predicate?: (androidWebView: AndroidWebView) => boolean, timeout?: number } | ((androidWebView: AndroidWebView) => boolean)): Promise<AndroidWebView>;
/**
* This method waits until [AndroidWebView] matching the `selector` is opened and returns it. If there is already an open
* [AndroidWebView] matching the `selector`, returns immediately.
* @param selector
* @param options
*/
webView(selector: {
/**
* Package identifier.
*/
pkg: string;
}, options?: {
/**
* Maximum time in milliseconds, defaults to 30 seconds, pass `0` to disable timeout. The default value can be changed by
* using the
* [androidDevice.setDefaultTimeout(timeout)](https://playwright.dev/docs/api/class-androiddevice#androiddevicesetdefaulttimeouttimeout)
* method.
*/
timeout?: number;
}): Promise<AndroidWebView>;
/**
* Currently open WebViews.
*/
webViews(): Array<AndroidWebView>;
}
export interface AndroidInput {
/**
* Performs a drag between `from` and `to` points.
* @param from The start point of the drag.
* @param to The end point of the drag.
* @param steps The number of steps in the drag. Each step takes 5 milliseconds to complete.
*/
drag(from: {
x: number;
y: number;
}, to: {
x: number;
y: number;
}, steps: number): Promise<void>;
/**
* Presses the `key`.
* @param key Key to press.
*/
press(key: AndroidKey): Promise<void>;
/**
* Swipes following the path defined by `segments`.
* @param from The point to start swiping from.
* @param segments Points following the `from` point in the swipe gesture.
* @param steps The number of steps for each segment. Each step takes 5 milliseconds to complete, so 100 steps means half a second per each segment.
*/
swipe(from: {
x: number;
y: number;
}, segments: Array<{
x: number;
y: number;
}>, steps: number): Promise<void>;
/**
* Taps at the specified `point`.
* @param point The point to tap at.
*/
tap(point: {
x: number;
y: number;
}): Promise<void>;
/**
* Types `text` into currently focused widget.
* @param text Text to type.
*/
type(text: string): Promise<void>;
}
/**
* [AndroidSocket] is a way to communicate with a process launched on the [AndroidDevice]. Use
* [androidDevice.open(command)](https://playwright.dev/docs/api/class-androiddevice#androiddeviceopencommand) to open a
* socket.
*/
export interface AndroidSocket {
/**
* Emitted when the socket is closed.
*/
on(event: 'close', listener: () => void): this;
/**
* Emitted when data is available to read from the socket.
*/
on(event: 'data', listener: (buffer: Buffer) => void): this;
/**
* Emitted when the socket is closed.
*/
once(event: 'close', listener: () => void): this;
/**
* Emitted when data is available to read from the socket.
*/
once(event: 'data', listener: (buffer: Buffer) => void): this;
/**
* Emitted when the socket is closed.
*/
addListener(event: 'close', listener: () => void): this;
/**
* Emitted when data is available to read from the socket.
*/
addListener(event: 'data', listener: (buffer: Buffer) => void): this;
/**
* Emitted when the socket is closed.
*/
removeListener(event: 'close', listener: () => void): this;
/**
* Emitted when data is available to read from the socket.
*/
removeListener(event: 'data', listener: (buffer: Buffer) => void): this;
/**
* Emitted when the socket is closed.
*/
off(event: 'close', listener: () => void): this;
/**
* Emitted when data is available to read from the socket.
*/
off(event: 'data', listener: (buffer: Buffer) => void): this;
/**
* Closes the socket.
*/
close(): Promise<void>;
/**
* Writes some `data` to the socket.
* @param data Data to write.
*/
write(data: Buffer): Promise<void>;
}
/**
* [AndroidWebView] represents a WebView open on the [AndroidDevice]. WebView is usually obtained using
* [androidDevice.webView(selector[, options])](https://playwright.dev/docs/api/class-androiddevice#androiddevicewebviewselector-options).
*/
export interface AndroidWebView {
/**
* Emitted when the WebView is closed.
*/
on(event: 'close', listener: () => void): this;
/**
* Emitted when the WebView is closed.
*/
once(event: 'close', listener: () => void): this;
/**
* Emitted when the WebView is closed.
*/
addListener(event: 'close', listener: () => void): this;
/**
* Emitted when the WebView is closed.
*/
removeListener(event: 'close', listener: () => void): this;
/**
* Emitted when the WebView is closed.
*/
off(event: 'close', listener: () => void): this;
/**
* Connects to the WebView and returns a regular Playwright [Page] to interact with.
*/
page(): Promise<Page>;
/**
* WebView process PID.
*/
pid(): number;
/**
* WebView package identifier.
*/
pkg(): string;
}
/**
* - extends: [EventEmitter]
*

View File

@ -230,5 +230,87 @@ export interface ElectronApplication {
evaluateHandle<R>(pageFunction: PageFunctionOn<typeof import('electron'), void, R>, arg?: any): Promise<SmartHandle<R>>;
}
export type AndroidElementInfo = {
clazz: string;
desc: string;
res: string;
pkg: string;
text: string;
bounds: { x: number, y: number, width: number, height: number };
checkable: boolean;
checked: boolean;
clickable: boolean;
enabled: boolean;
focusable: boolean;
focused: boolean;
longClickable: boolean;
scrollable: boolean;
selected: boolean;
};
export type AndroidSelector = {
checkable?: boolean,
checked?: boolean,
clazz?: string | RegExp,
clickable?: boolean,
depth?: number,
desc?: string | RegExp,
enabled?: boolean,
focusable?: boolean,
focused?: boolean,
hasChild?: { selector: AndroidSelector },
hasDescendant?: { selector: AndroidSelector, maxDepth?: number },
longClickable?: boolean,
pkg?: string | RegExp,
res?: string | RegExp,
scrollable?: boolean,
selected?: boolean,
text?: string | RegExp,
};
export type AndroidKey =
'Unknown' |
'SoftLeft' | 'SoftRight' |
'Home' |
'Back' |
'Call' | 'EndCall' |
'0' | '1' | '2' | '3' | '4' | '5' | '6' | '7' | '8' | '9' |
'Star' | 'Pound' | '*' | '#' |
'DialUp' | 'DialDown' | 'DialLeft' | 'DialRight' | 'DialCenter' |
'VolumeUp' | 'VolumeDown' |
'Power' |
'Camera' |
'Clear' |
'A' | 'B' | 'C' | 'D' | 'E' | 'F' | 'G' | 'H' | 'I' | 'J' | 'K' | 'L' | 'M' |
'N' | 'O' | 'P' | 'Q' | 'R' | 'S' | 'T' | 'U' | 'V' | 'W' | 'X' | 'Y' | 'Z' |
'Comma' | ',' |
'Period' | '.' |
'AltLeft' | 'AltRight' |
'ShiftLeft' | 'ShiftRight' |
'Tab' | '\t' |
'Space' | ' ' |
'Sym' |
'Explorer' |
'Envelop' |
'Enter' | '\n' |
'Del' |
'Grave' |
'Minus' | '-' |
'Equals' | '=' |
'LeftBracket' | '(' |
'RightBracket' | ')' |
'Backslash' | '\\' |
'Semicolon' | ';' |
'Apostrophe' | '`' |
'Slash' | '/' |
'At' |
'Num' |
'HeadsetHook' |
'Focus' |
'Plus' | '+' |
'Menu' |
'Notification' |
'Search';
// This is required to not export everything by default. See https://github.com/Microsoft/TypeScript/issues/19545#issuecomment-340490459
export {};