Skip to content

Commit ad5c028

Browse files
authored
test(android): run selected page tests on android (#5879)
1 parent cbebf64 commit ad5c028

22 files changed

+188
-178
lines changed

.github/workflows/tests.yml

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -235,6 +235,9 @@ jobs:
235235
test_android:
236236
name: Android Emulator
237237
runs-on: macos-10.15
238+
env:
239+
FOLIO_JSON_OUTPUT_NAME: "test-results/report.json"
240+
PW_ANDROID_TESTS: 1
238241
steps:
239242
- uses: actions/checkout@v2
240243
- uses: actions/setup-node@v1
@@ -247,10 +250,10 @@ jobs:
247250
run: utils/avd_recreate.sh
248251
- name: Start Android Emulator
249252
run: utils/avd_start.sh
250-
- run: npx folio test/android -p browserName=chromium --workers=1 --forbid-only --timeout=120000 --global-timeout=5400000 --retries=3 --reporter=dot,json
251-
env:
252-
FOLIO_JSON_OUTPUT_NAME: "test-results/report.json"
253-
PW_ANDROID_TESTS: 1
253+
- name: Run device tests
254+
run: npx folio test/android -p browserName=chromium --workers=1 --forbid-only --timeout=120000 --global-timeout=5400000 --retries=3 --reporter=dot,json
255+
- name: Run page tests
256+
run: npx folio test/page -p browserName=chromium --workers=1 --forbid-only --timeout=120000 --global-timeout=5400000 --retries=3 --reporter=dot,json
254257
- run: ./utils/upload_flakiness_dashboard.sh ./test-results/report.json
255258
if: always() && (github.ref == 'refs/heads/master' || startsWith(github.ref, 'refs/heads/release-'))
256259
- uses: actions/upload-artifact@v1

test/android/android.fixtures.ts

Lines changed: 0 additions & 35 deletions
This file was deleted.

test/android/browser.spec.ts

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -14,42 +14,42 @@
1414
* limitations under the License.
1515
*/
1616

17-
import { folio } from './android.fixtures';
17+
import { folio } from '../fixtures';
1818
const { it, expect } = folio;
1919

2020
if (process.env.PW_ANDROID_TESTS) {
21-
it('androidDevice.model', async function({ device }) {
22-
expect(device.model()).toBe('sdk_gphone_x86_arm');
21+
it('androidDevice.model', async function({ androidDevice }) {
22+
expect(androidDevice.model()).toBe('sdk_gphone_x86_arm');
2323
});
2424

25-
it('androidDevice.launchBrowser', async function({ device }) {
26-
const context = await device.launchBrowser();
25+
it('androidDevice.launchBrowser', async function({ androidDevice }) {
26+
const context = await androidDevice.launchBrowser();
2727
const [page] = context.pages();
2828
await page.goto('data:text/html,<title>Hello world!</title>');
2929
expect(await page.title()).toBe('Hello world!');
3030
await context.close();
3131
});
3232

33-
it('should create new page', async function({ device }) {
34-
const context = await device.launchBrowser();
33+
it('should create new page', async function({ androidDevice }) {
34+
const context = await androidDevice.launchBrowser();
3535
const page = await context.newPage();
3636
await page.goto('data:text/html,<title>Hello world!</title>');
3737
expect(await page.title()).toBe('Hello world!');
3838
await page.close();
3939
await context.close();
4040
});
4141

42-
it('should check', async function({ device }) {
43-
const context = await device.launchBrowser();
42+
it('should check', async function({ androidDevice }) {
43+
const context = await androidDevice.launchBrowser();
4444
const [page] = context.pages();
4545
await page.setContent(`<input id='checkbox' type='checkbox'></input>`);
4646
await page.check('input');
4747
expect(await page.evaluate(() => window['checkbox'].checked)).toBe(true);
4848
await page.close();
4949
await context.close();
5050
});
51-
it('should be able to send CDP messages', async ({ device }) => {
52-
const context = await device.launchBrowser();
51+
it('should be able to send CDP messages', async ({ androidDevice }) => {
52+
const context = await androidDevice.launchBrowser();
5353
const [page] = context.pages();
5454
const client = await context.newCDPSession(page);
5555
await client.send('Runtime.enable');

test/android/device.spec.ts

Lines changed: 15 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -17,17 +17,17 @@
1717
import fs from 'fs';
1818
import { PNG } from 'pngjs';
1919

20-
import { folio } from './android.fixtures';
20+
import { folio } from '../fixtures';
2121
const { it, expect } = folio;
2222

2323
if (process.env.PW_ANDROID_TESTS) {
24-
it('androidDevice.shell', async function({ device }) {
25-
const output = await device.shell('echo 123');
24+
it('androidDevice.shell', async function({ androidDevice }) {
25+
const output = await androidDevice.shell('echo 123');
2626
expect(output.toString()).toBe('123\n');
2727
});
2828

29-
it('androidDevice.open', async function({ device }) {
30-
const socket = await device.open('shell:/bin/cat');
29+
it('androidDevice.open', async function({ androidDevice }) {
30+
const socket = await androidDevice.open('shell:/bin/cat');
3131
await socket.write(Buffer.from('321\n'));
3232
const output = await new Promise(resolve => socket.on('data', resolve));
3333
expect(output.toString()).toBe('321\n');
@@ -36,28 +36,28 @@ if (process.env.PW_ANDROID_TESTS) {
3636
await closedPromise;
3737
});
3838

39-
it('androidDevice.screenshot', async function({ device, testInfo }) {
39+
it('androidDevice.screenshot', async function({ androidDevice, testInfo }) {
4040
const path = testInfo.outputPath('screenshot.png');
41-
const result = await device.screenshot({ path });
41+
const result = await androidDevice.screenshot({ path });
4242
const buffer = fs.readFileSync(path);
4343
expect(result.length).toBe(buffer.length);
4444
const { width, height} = PNG.sync.read(result);
4545
expect(width).toBe(1080);
4646
expect(height).toBe(1920);
4747
});
4848

49-
it('androidDevice.push', async function({ device, testInfo }) {
50-
await device.shell('rm /data/local/tmp/hello-world');
51-
await device.push(Buffer.from('hello world'), '/data/local/tmp/hello-world');
52-
const data = await device.shell('cat /data/local/tmp/hello-world');
49+
it('androidDevice.push', async function({ androidDevice }) {
50+
await androidDevice.shell('rm /data/local/tmp/hello-world');
51+
await androidDevice.push(Buffer.from('hello world'), '/data/local/tmp/hello-world');
52+
const data = await androidDevice.shell('cat /data/local/tmp/hello-world');
5353
expect(data).toEqual(Buffer.from('hello world'));
5454
});
5555

5656
it('androidDevice.fill', test => {
5757
test.fixme(!!process.env.CI, 'Hangs on the bots');
58-
}, async function({ device }) {
59-
await device.shell('am start org.chromium.webview_shell/.WebViewBrowserActivity');
60-
await device.fill({ res: 'org.chromium.webview_shell:id/url_field' }, 'Hello');
61-
expect((await device.info({ res: 'org.chromium.webview_shell:id/url_field' })).text).toBe('Hello');
58+
}, async function({ androidDevice }) {
59+
await androidDevice.shell('am start org.chromium.webview_shell/.WebViewBrowserActivity');
60+
await androidDevice.fill({ res: 'org.chromium.webview_shell:id/url_field' }, 'Hello');
61+
expect((await androidDevice.info({ res: 'org.chromium.webview_shell:id/url_field' })).text).toBe('Hello');
6262
});
6363
}

test/android/webview.spec.ts

Lines changed: 20 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -14,47 +14,47 @@
1414
* limitations under the License.
1515
*/
1616

17-
import { folio } from './android.fixtures';
17+
import { folio } from '../fixtures';
1818
const { it, expect } = folio;
1919

2020
if (process.env.PW_ANDROID_TESTS) {
21-
it('androidDevice.webView', async function({ device }) {
22-
expect(device.webViews().length).toBe(0);
23-
await device.shell('am start org.chromium.webview_shell/.WebViewBrowserActivity');
24-
const webview = await device.webView({ pkg: 'org.chromium.webview_shell' });
21+
it('androidDevice.webView', async function({ androidDevice }) {
22+
expect(androidDevice.webViews().length).toBe(0);
23+
await androidDevice.shell('am start org.chromium.webview_shell/.WebViewBrowserActivity');
24+
const webview = await androidDevice.webView({ pkg: 'org.chromium.webview_shell' });
2525
expect(webview.pkg()).toBe('org.chromium.webview_shell');
26-
expect(device.webViews().length).toBe(1);
26+
expect(androidDevice.webViews().length).toBe(1);
2727
});
2828

29-
it('webView.page', async function({ device }) {
30-
expect(device.webViews().length).toBe(0);
31-
await device.shell('am start org.chromium.webview_shell/.WebViewBrowserActivity');
32-
const webview = await device.webView({ pkg: 'org.chromium.webview_shell' });
29+
it('webView.page', async function({ androidDevice }) {
30+
expect(androidDevice.webViews().length).toBe(0);
31+
await androidDevice.shell('am start org.chromium.webview_shell/.WebViewBrowserActivity');
32+
const webview = await androidDevice.webView({ pkg: 'org.chromium.webview_shell' });
3333
const page = await webview.page();
3434
expect(page.url()).toBe('about:blank');
3535
});
3636

37-
it('should navigate page internally', async function({ device, server }) {
38-
expect(device.webViews().length).toBe(0);
39-
await device.shell('am start org.chromium.webview_shell/.WebViewBrowserActivity');
40-
const webview = await device.webView({ pkg: 'org.chromium.webview_shell' });
37+
it('should navigate page internally', async function({ androidDevice }) {
38+
expect(androidDevice.webViews().length).toBe(0);
39+
await androidDevice.shell('am start org.chromium.webview_shell/.WebViewBrowserActivity');
40+
const webview = await androidDevice.webView({ pkg: 'org.chromium.webview_shell' });
4141
const page = await webview.page();
4242
await page.goto('data:text/html,<title>Hello world!</title>');
4343
expect(await page.title()).toBe('Hello world!');
4444
});
4545

4646
it('should navigate page externally', test => {
4747
test.fixme(!!process.env.CI, 'Hangs on the bots');
48-
}, async function({ device }) {
49-
expect(device.webViews().length).toBe(0);
50-
await device.shell('am start org.chromium.webview_shell/.WebViewBrowserActivity');
51-
const webview = await device.webView({ pkg: 'org.chromium.webview_shell' });
48+
}, async function({ androidDevice }) {
49+
expect(androidDevice.webViews().length).toBe(0);
50+
await androidDevice.shell('am start org.chromium.webview_shell/.WebViewBrowserActivity');
51+
const webview = await androidDevice.webView({ pkg: 'org.chromium.webview_shell' });
5252
const page = await webview.page();
5353

54-
await device.fill({ res: 'org.chromium.webview_shell:id/url_field' }, 'data:text/html,<title>Hello world!</title>');
54+
await androidDevice.fill({ res: 'org.chromium.webview_shell:id/url_field' }, 'data:text/html,<title>Hello world!</title>');
5555
await Promise.all([
5656
page.waitForNavigation(),
57-
device.press({ res: 'org.chromium.webview_shell:id/url_field' }, 'Enter')
57+
androidDevice.press({ res: 'org.chromium.webview_shell:id/url_field' }, 'Enter')
5858
]);
5959
expect(await page.title()).toBe('Hello world!');
6060
});
Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
/**
2+
* Copyright 2018 Google Inc. All rights reserved.
3+
* Modifications copyright (c) Microsoft Corporation.
4+
*
5+
* Licensed under the Apache License, Version 2.0 (the "License");
6+
* you may not use this file except in compliance with the License.
7+
* You may obtain a copy of the License at
8+
*
9+
* http://www.apache.org/licenses/LICENSE-2.0
10+
*
11+
* Unless required by applicable law or agreed to in writing, software
12+
* distributed under the License is distributed on an "AS IS" BASIS,
13+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
* See the License for the specific language governing permissions and
15+
* limitations under the License.
16+
*/
17+
18+
import { it, expect } from './fixtures';
19+
import path from 'path';
20+
21+
it('should work with browser context scripts', async ({ context, server }) => {
22+
await context.addInitScript(() => window['temp'] = 123);
23+
const page = await context.newPage();
24+
await page.addInitScript(() => window['injected'] = window['temp']);
25+
await page.goto(server.PREFIX + '/tamperable.html');
26+
expect(await page.evaluate(() => window['result'])).toBe(123);
27+
});
28+
29+
it('should work without navigation, after all bindings', async ({ context }) => {
30+
let callback;
31+
const promise = new Promise(f => callback = f);
32+
await context.exposeFunction('woof', function(arg) {
33+
callback(arg);
34+
});
35+
36+
await context.addInitScript(() => {
37+
window['woof']('hey');
38+
window['temp'] = 123;
39+
});
40+
const page = await context.newPage();
41+
42+
expect(await page.evaluate(() => window['temp'])).toBe(123);
43+
expect(await promise).toBe('hey');
44+
});
45+
46+
it('should work without navigation in popup', async ({ context }) => {
47+
await context.addInitScript(() => window['temp'] = 123);
48+
const page = await context.newPage();
49+
const [popup] = await Promise.all([
50+
page.waitForEvent('popup'),
51+
page.evaluate(() => window['win'] = window.open()),
52+
]);
53+
expect(await popup.evaluate(() => window['temp'])).toBe(123);
54+
});
55+
56+
it('should work with browser context scripts with a path', async ({ context, server }) => {
57+
await context.addInitScript({ path: path.join(__dirname, 'assets/injectedfile.js') });
58+
const page = await context.newPage();
59+
await page.goto(server.PREFIX + '/tamperable.html');
60+
expect(await page.evaluate(() => window['result'])).toBe(123);
61+
});
62+
63+
it('should work with browser context scripts for already created pages', async ({ context, server }) => {
64+
const page = await context.newPage();
65+
await context.addInitScript(() => window['temp'] = 123);
66+
await page.addInitScript(() => window['injected'] = window['temp']);
67+
await page.goto(server.PREFIX + '/tamperable.html');
68+
expect(await page.evaluate(() => window['result'])).toBe(123);
69+
});

test/fixtures.ts

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ import fs from 'fs';
2020
import path from 'path';
2121
import util from 'util';
2222
import os from 'os';
23-
import type { Browser, BrowserContext, BrowserType, Page } from '../index';
23+
import type { AndroidDevice, Browser, BrowserContext, BrowserType, Page } from '../index';
2424
import { installCoverageHooks } from './coverage';
2525
import { folio as httpFolio } from './http.fixtures';
2626
import { folio as playwrightFolio } from './playwright.fixtures';
@@ -45,6 +45,8 @@ type ModeParameters = {
4545
};
4646
type WorkerFixtures = {
4747
toImpl: (rpcObject: any) => any;
48+
androidDevice: AndroidDevice;
49+
androidDeviceBrowser: BrowserContext;
4850
};
4951
type TestFixtures = {
5052
createUserDataDir: () => Promise<string>;
@@ -157,6 +159,27 @@ fixtures.testParametersPathSegment.override(async ({ browserName }, run) => {
157159
await run(browserName);
158160
});
159161

162+
fixtures.androidDevice.init(async ({ playwright }, runTest) => {
163+
const [device] = await playwright._android.devices();
164+
await device.shell('am force-stop org.chromium.webview_shell');
165+
await device.shell('am force-stop com.android.chrome');
166+
device.setDefaultTimeout(120000);
167+
await runTest(device);
168+
await device.close();
169+
}, { scope: 'worker' });
170+
171+
fixtures.androidDeviceBrowser.init(async ({ androidDevice }, runTest) => {
172+
await runTest(await androidDevice.launchBrowser());
173+
}, { scope: 'worker' });
174+
175+
if (process.env.PW_ANDROID_TESTS) {
176+
fixtures.page.override(async ({ androidDeviceBrowser }, run) => {
177+
for (const page of androidDeviceBrowser.pages())
178+
await page.close();
179+
run(await androidDeviceBrowser.newPage());
180+
});
181+
}
182+
160183
export const folio = fixtures.build();
161184

162185
folio.generateParametrizedTests(

0 commit comments

Comments
 (0)