Skip to content

Commit

Permalink
feat(adb): add screenshot (#4701)
Browse files Browse the repository at this point in the history
  • Loading branch information
pavelfeldman committed Dec 14, 2020
1 parent 1596b53 commit 4799e8f
Show file tree
Hide file tree
Showing 13 changed files with 63 additions and 12 deletions.
3 changes: 1 addition & 2 deletions .github/workflows/tests.yml
Expand Up @@ -246,11 +246,10 @@ jobs:
run: utils/avd_recreate.sh
- name: Start Android Emulator
run: utils/avd_start.sh
- run: npx folio test/android -p browserName=chromium --workers=1 --forbid-only --global-timeout=5400000 --retries=3 --reporter=dot,json
- run: npx folio test/android -p browserName=chromium --workers=1 --forbid-only --timeout=60000 --global-timeout=5400000 --retries=3 --reporter=dot,json
env:
FOLIO_JSON_OUTPUT_NAME: "test-results/report.json"
PW_ANDROID_TESTS: 1
DEBUG: pw:api
- run: ./utils/upload_flakiness_dashboard.sh ./test-results/report.json
if: always() && github.ref == 'refs/heads/master'
- uses: actions/upload-artifact@v1
Expand Down
1 change: 1 addition & 0 deletions android-types-internal.d.ts
Expand Up @@ -47,6 +47,7 @@ export interface AndroidDevice<BrowserContextOptions, BrowserContext, Page> exte
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 {
Expand Down
4 changes: 2 additions & 2 deletions package.json
Expand Up @@ -12,6 +12,7 @@
"ctest": "cross-env BROWSER=chromium folio test/",
"ftest": "cross-env BROWSER=firefox folio test/",
"wtest": "cross-env BROWSER=webkit folio test/",
"atest": "cross-env BROWSER=chromium PW_ANDROID_TESTS=1 npx folio test/android --workers=1 --reporter=list",
"test": "folio test/",
"eslint": "[ \"$CI\" = true ] && eslint --quiet -f codeframe --ext js,ts . || eslint --ext js,ts .",
"tsc": "tsc -p .",
Expand All @@ -31,8 +32,7 @@
"roll-browser": "node utils/roll_browser.js",
"coverage": "node test/checkCoverage.js",
"check-deps": "node utils/check_deps.js",
"build-android-driver": "./utils/build_android_driver.sh",
"test-android-driver": "PW_ANDROID_TESTS=1 npx folio test/android -p browserName=chromium --workers=1"
"build-android-driver": "./utils/build_android_driver.sh"
},
"author": {
"name": "Microsoft Corporation"
Expand Down
10 changes: 10 additions & 0 deletions src/client/android.ts
Expand Up @@ -191,6 +191,16 @@ export class AndroidDevice extends ChannelOwner<channels.AndroidDeviceChannel, c
});
}

async screenshot(options: { path?: string } = {}): Promise<Buffer> {
return await this._wrapApiCall('androidDevice.screenshot', async () => {
const { binary } = await this._channel.screenshot();
const buffer = Buffer.from(binary, 'base64');
if (options.path)
await util.promisify(fs.writeFile)(options.path, buffer);
return buffer;
});
}

async close() {
return this._wrapApiCall('androidDevice.close', async () => {
await this._channel.close();
Expand Down
4 changes: 4 additions & 0 deletions src/dispatchers/androidDispatcher.ts
Expand Up @@ -136,6 +136,10 @@ export class AndroidDeviceDispatcher extends Dispatcher<AndroidDevice, channels.
await this._object.send('inputDrag', params);
}

async screenshot(params: channels.AndroidDeviceScreenshotParams): Promise<channels.AndroidDeviceScreenshotResult> {
return { binary: (await this._object.screenshot()).toString('base64') };
}

async shell(params: channels.AndroidDeviceShellParams): Promise<channels.AndroidDeviceShellResult> {
return { result: (await this._object.shell(params.command)).toString('base64') };
}
Expand Down
6 changes: 6 additions & 0 deletions src/protocol/channels.ts
Expand Up @@ -2462,6 +2462,7 @@ export interface AndroidDeviceChannel extends Channel {
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>;
inputTap(params: AndroidDeviceInputTapParams, metadata?: Metadata): Promise<AndroidDeviceInputTapResult>;
Expand Down Expand Up @@ -2601,6 +2602,11 @@ export type AndroidDeviceTreeOptions = {};
export type AndroidDeviceTreeResult = {
tree: AndroidElementInfo,
};
export type AndroidDeviceScreenshotParams = {};
export type AndroidDeviceScreenshotOptions = {};
export type AndroidDeviceScreenshotResult = {
binary: Binary,
};
export type AndroidDeviceInputTypeParams = {
text: string,
};
Expand Down
4 changes: 4 additions & 0 deletions src/protocol/protocol.yml
Expand Up @@ -2204,6 +2204,10 @@ AndroidDevice:
returns:
tree: AndroidElementInfo

screenshot:
returns:
binary: binary

inputType:
parameters:
text: string
Expand Down
1 change: 1 addition & 0 deletions src/protocol/validator.ts
Expand Up @@ -966,6 +966,7 @@ export function createScheme(tChannel: (name: string) => Validator): Scheme {
selector: tType('AndroidSelector'),
});
scheme.AndroidDeviceTreeParams = tOptional(tObject({}));
scheme.AndroidDeviceScreenshotParams = tOptional(tObject({}));
scheme.AndroidDeviceInputTypeParams = tObject({
text: tString,
});
Expand Down
4 changes: 4 additions & 0 deletions src/server/android/android.ts
Expand Up @@ -148,6 +148,10 @@ export class AndroidDevice extends EventEmitter {
return await this._backend.open(`${command}`);
}

async screenshot(): Promise<Buffer> {
return await this._backend.runCommand(`shell:screencap -p`);
}

private async _driver(): Promise<Transport> {
if (this._driverPromise)
return this._driverPromise;
Expand Down
4 changes: 2 additions & 2 deletions test/android/browser.spec.ts
Expand Up @@ -18,11 +18,11 @@ import { folio } from './android.fixtures';
const { it, expect } = folio;

if (process.env.PW_ANDROID_TESTS) {
it('should discover device', async function({ device }) {
it('androidDevice.model', async function({ device }) {
expect(device.model()).toBe('sdk_gphone_x86_arm');
});

it('should launch browser', async function({ device }) {
it('androidDevice.launchBrowser', async function({ device }) {
const context = await device.launchBrowser();
const [page] = context.pages();
await page.goto('data:text/html,<title>Hello world!</title>');
Expand Down
24 changes: 22 additions & 2 deletions test/android/device.spec.ts
Expand Up @@ -14,19 +14,39 @@
* limitations under the License.
*/

import * as fs from 'fs';
import { PNG } from 'pngjs';

import { folio } from './android.fixtures';
const { it, expect } = folio;

if (process.env.PW_ANDROID_TESTS) {
it('should run ADB shell commands', async function({ device }) {
it('androidDevice.shell', async function({ device }) {
const output = await device.shell('echo 123');
expect(output.toString()).toBe('123\n');
});

it('should open a ADB socket', async function({ device }) {
it('androidDevice.open', async function({ device }) {
const socket = await device.open('shell:/bin/cat');
await socket.write(Buffer.from('321\n'));
const output = await new Promise(resolve => socket.on('data', resolve));
expect(output.toString()).toBe('321\n');
});

it('androidDevice.screenshot', async function({ device, testInfo }) {
const path = testInfo.outputPath('screenshot.png');
const result = await device.screenshot({ path });
const buffer = fs.readFileSync(path);
expect(result.length).toBe(buffer.length);
const { width, height} = PNG.sync.read(result);
expect(width).toBe(1080);
expect(height).toBe(1920);
});

it('androidDevice.push', async function({ device, testInfo }) {
await device.shell('rm /data/local/tmp/hello-world');
await device.push(Buffer.from('hello world'), '/data/local/tmp/hello-world');
const data = await device.shell('cat /data/local/tmp/hello-world');
expect(data).toEqual(Buffer.from('hello world'));
});
}
8 changes: 5 additions & 3 deletions test/android/webview.spec.ts
Expand Up @@ -18,15 +18,15 @@ import { folio } from './android.fixtures';
const { it, expect } = folio;

if (process.env.PW_ANDROID_TESTS) {
it('should discover webviews', async function({ device }) {
it('androidDevice.webView', async function({ device }) {
expect(device.webViews().length).toBe(0);
await device.shell('am start org.chromium.webview_shell/.WebViewBrowserActivity');
const webview = await device.webView({ pkg: 'org.chromium.webview_shell' });
expect(webview.pkg()).toBe('org.chromium.webview_shell');
expect(device.webViews().length).toBe(1);
});

it('should connect to page', async function({ device }) {
it('webView.page', async function({ device }) {
expect(device.webViews().length).toBe(0);
await device.shell('am start org.chromium.webview_shell/.WebViewBrowserActivity');
const webview = await device.webView({ pkg: 'org.chromium.webview_shell' });
Expand All @@ -43,7 +43,9 @@ if (process.env.PW_ANDROID_TESTS) {
expect(await page.title()).toBe('Hello world!');
});

it('should navigate page externally', async function({ device, server }) {
it('should navigate page externally', test => {
test.fixme(!!process.env.CI, 'Hangs on the bots');
}, async function({ device, server }) {
expect(device.webViews().length).toBe(0);
await device.shell('am start org.chromium.webview_shell/.WebViewBrowserActivity');
const webview = await device.webView({ pkg: 'org.chromium.webview_shell' });
Expand Down
2 changes: 1 addition & 1 deletion utils/avd_recreate.sh
Expand Up @@ -10,5 +10,5 @@ fi

${ANDROID_HOME}/tools/bin/avdmanager delete avd --name android30 || true
echo "y" | ${ANDROID_HOME}/tools/bin/sdkmanager --install "system-images;android-30;google_apis;x86"
echo "no" | ${ANDROID_HOME}/tools/bin/avdmanager create avd --force --name android30 --device pixel_4 --package "system-images;android-30;google_apis;x86"
echo "no" | ${ANDROID_HOME}/tools/bin/avdmanager create avd --force --name android30 --device "Nexus 5X" --package "system-images;android-30;google_apis;x86"
${ANDROID_HOME}/emulator/emulator -list-avds

0 comments on commit 4799e8f

Please sign in to comment.