참고소스 수정본

This commit is contained in:
LASTA_DEV01\lasta
2026-05-12 19:40:31 +09:00
parent 0f34a451fc
commit 2e9204243d
8708 changed files with 3259488 additions and 869 deletions

View File

@@ -0,0 +1,58 @@
/**
* Copyright 2020 Microsoft Corporation. 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 net from 'net';
import { androidTest as test, expect } from './androidTest';
// Force a separate worker to avoid messing up with `androidDevice` fixture.
test.use({ launchOptions: [async ({ launchOptions }, use) => use(launchOptions), { scope: 'worker' }] });
test('androidDevice.close', async function({ playwright }) {
const devices = await playwright._android.devices();
expect(devices.length).toBe(1);
const device = devices[0];
const events: string[] = [];
device.on('close', () => events.push('close'));
await device.close();
await device.close();
expect(events).toEqual(['close']);
});
test('should be able to use a custom port', async function({ playwright }) {
const proxyPort = 5038;
let countOfIncomingConnections = 0;
let countOfConnections = 0;
const server = net.createServer(socket => {
++countOfIncomingConnections;
++countOfConnections;
socket.on('close', () => countOfConnections--);
const client = net.connect(5037, '127.0.0.1');
socket.pipe(client).pipe(socket);
});
await new Promise<void>(resolve => server.listen(proxyPort, resolve));
const devices = await playwright._android.devices({ port: proxyPort });
expect(countOfIncomingConnections).toBeGreaterThanOrEqual(1);
expect(devices).toHaveLength(1);
const device = devices[0];
const value = await device.shell('echo foobar');
expect(value.toString()).toBe('foobar\n');
await device.close();
await new Promise(resolve => server.close(resolve));
expect(countOfIncomingConnections).toBeGreaterThanOrEqual(1);
expect(countOfConnections).toBe(0);
});

View File

@@ -0,0 +1,88 @@
/**
* 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 { baseTest } from '../config/baseTest';
import type { PageTestFixtures, PageWorkerFixtures } from '../page/pageTestApi';
import type { AndroidDevice, BrowserContext } from 'playwright-core';
export { expect } from '@playwright/test';
type AndroidTestFixtures = {
androidDevice: AndroidDevice;
};
type AndroidWorkerFixtures = PageWorkerFixtures & {
androidDeviceWorker: AndroidDevice;
androidContext: BrowserContext;
};
async function closeAllActivities(device: AndroidDevice) {
await device.shell('am force-stop com.google.android.googlequicksearchbox');
await device.shell('am force-stop org.chromium.webview_shell');
await device.shell('am force-stop com.android.chrome');
}
export const androidTest = baseTest.extend<PageTestFixtures & AndroidTestFixtures, AndroidWorkerFixtures>({
androidDeviceWorker: [async ({ playwright }, run) => {
const device = (await playwright._android.devices())[0];
await closeAllActivities(device);
device.setDefaultTimeout(90000);
await run(device);
await device.close();
}, { scope: 'worker' }],
browserVersion: [async ({ androidDeviceWorker }, run) => {
const browserVersion = (await androidDeviceWorker.shell('dumpsys package com.android.chrome'))
.toString('utf8')
.split('\n')
.find(line => line.includes('versionName='))!
.trim()
.split('=')[1];
await run(browserVersion);
}, { scope: 'worker' }],
browserMajorVersion: [async ({ browserVersion }, run) => {
await run(Number(browserVersion.split('.')[0]));
}, { scope: 'worker' }],
isBidi: [false, { scope: 'worker' }],
isAndroid: [true, { scope: 'worker' }],
isElectron: [false, { scope: 'worker' }],
electronMajorVersion: [0, { scope: 'worker' }],
isHeadlessShell: [false, { scope: 'worker' }],
isFrozenWebkit: [false, { scope: 'worker' }],
androidDevice: async ({ androidDeviceWorker }, use) => {
await closeAllActivities(androidDeviceWorker);
await use(androidDeviceWorker);
await closeAllActivities(androidDeviceWorker);
},
androidContext: [async ({ androidDeviceWorker }, run) => {
const context = await androidDeviceWorker.launchBrowser();
const [page] = context.pages();
await page.goto('data:text/html,Default page');
await run(context);
}, { scope: 'worker' }],
page: async ({ androidContext }, run) => {
// Retain default page, otherwise Clank will re-create it.
while (androidContext.pages().length > 1)
await androidContext.pages()[1].close();
const page = await androidContext.newPage();
await run(page);
await androidContext.clearCookies();
},
});

View File

@@ -0,0 +1,170 @@
/**
* Copyright 2020 Microsoft Corporation. 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 fs from 'fs';
import { androidTest as test, expect } from './androidTest';
test('androidDevice.model', async function({ androidDevice }) {
expect(androidDevice.model()).toContain('sdk_gphone');
expect(androidDevice.model()).toContain('x86_64');
});
test('androidDevice.launchBrowser', async function({ androidDevice }) {
const context = await androidDevice.launchBrowser();
const [page] = context.pages();
await page.goto('data:text/html,<title>Hello world!</title>');
expect(await page.title()).toBe('Hello world!');
await context.close();
});
test('androidDevice.launchBrowser should treat args correctly', async ({ androidDevice }) => {
for (const arg of [
"--user-agent='I am Foo'",
'--user-agent="I am Foo"',
]) {
await test.step(`arg: ${arg}`, async () => {
const context = await androidDevice.launchBrowser({ args: [arg] });
const page = await context.newPage();
const userAgent = await page.evaluate(() => navigator.userAgent);
await context.close();
expect(userAgent).toBe('I am Foo');
});
}
});
test('androidDevice.launchBrowser should throw for bad proxy server value', async ({ androidDevice }) => {
const error = await androidDevice.launchBrowser({
// @ts-expect-error server must be a string
proxy: { server: 123 }
}).catch(e => e);
expect(error.message).toContain('proxy.server: expected string, got number');
});
test('androidDevice.launchBrowser should pass proxy config', async ({ androidDevice, server, mode, loopback }) => {
server.setRoute('/target.html', async (req, res) => {
res.end('<html><title>Served by the proxy</title></html>');
});
const context = await androidDevice.launchBrowser({ proxy: { server: `${loopback}:${server.PORT}` } });
const page = await context.newPage();
await page.goto('http://non-existent.com/target.html');
expect(await page.title()).toBe('Served by the proxy');
await context.close();
});
test('should create new page', async function({ androidDevice }) {
const context = await androidDevice.launchBrowser();
const page = await context.newPage();
await page.goto('data:text/html,<title>Hello world!</title>');
expect(await page.title()).toBe('Hello world!');
await page.close();
await context.close();
});
test('should check', async function({ androidDevice }) {
const context = await androidDevice.launchBrowser();
const [page] = context.pages();
await page.setContent(`<input id='checkbox' type='checkbox'></input>`);
await page.check('input');
expect(await page.evaluate(() => window['checkbox'].checked)).toBe(true);
await page.close();
await context.close();
});
test('should take page screenshot', async function({ androidDevice }) {
test.info().annotations.push({ type: 'issue', description: 'https://github.com/microsoft/playwright/issues/26342' });
test.fixme(true, 'Sometimes fails with Protocol error (Page.captureScreenshot): Unable to capture screenshot');
test.fixme(true, 'Regular screenshot has an extra pixel border');
test.fixme(true, 'Full page screenshot has repeated content');
const context = await androidDevice.launchBrowser();
const [page] = context.pages();
await page.setContent(`
<html lang="en">
<head>
<meta name="viewport" content="width=device-width, initial-scale=1.0">
</head>
<body>
<style>
* { margin: 0; padding: 0; }
div {
width: 200px;
height: 250px;
}
</style>
<div style="background: red"></div>
<div style="background: green"></div>
<div style="background: red"></div>
<div style="background: green"></div>
<div style="background: red"></div>
<div style="background: green"></div>
</body>
</html>
`);
const screenshot = await page.screenshot({ fullPage: false, scale: 'css' });
const fullPageScreenshot = await page.screenshot({ fullPage: true, scale: 'css' });
expect(screenshot).toMatchSnapshot('page-screenshot.png');
expect(fullPageScreenshot).toMatchSnapshot('fullpage-screenshot.png');
await context.close();
});
test('should be able to send CDP messages', async ({ androidDevice }) => {
const context = await androidDevice.launchBrowser();
const [page] = context.pages();
const client = await context.newCDPSession(page);
await client.send('Runtime.enable');
const evalResponse = await client.send('Runtime.evaluate', { expression: '1 + 2', returnByValue: true });
expect(evalResponse.result.value).toBe(3);
await context.close();
});
test('should be able to pass context options', async ({ androidDevice, httpsServer }) => {
const context = await androidDevice.launchBrowser({
colorScheme: 'dark',
geolocation: { longitude: 10, latitude: 10 },
permissions: ['geolocation'],
ignoreHTTPSErrors: true,
baseURL: httpsServer.PREFIX,
});
const [page] = context.pages();
await page.goto('./empty.html');
expect(page.url()).toBe(httpsServer.PREFIX + '/empty.html');
expect(await page.evaluate(() => new Promise(resolve => navigator.geolocation.getCurrentPosition(position => {
resolve({ latitude: position.coords.latitude, longitude: position.coords.longitude });
})))).toEqual({ latitude: 10, longitude: 10 });
expect(await page.evaluate(() => matchMedia('(prefers-color-scheme: dark)').matches)).toBe(true);
expect(await page.evaluate(() => matchMedia('(prefers-color-scheme: light)').matches)).toBe(false);
await context.close();
});
test('should record har', async ({ androidDevice }) => {
test.info().annotations.push({ type: 'issue', description: 'https://github.com/microsoft/playwright/issues/28015' });
const harPath = test.info().outputPath('test.har');
const context = await androidDevice.launchBrowser({
recordHar: { path: harPath }
});
const [page] = context.pages();
await page.goto('data:text/html,<title>Hello</title>');
await page.waitForLoadState('domcontentloaded');
await context.close();
const log = JSON.parse(fs.readFileSync(harPath).toString())['log'];
expect(log.pages[0].title).toBe('Hello');
});

View File

@@ -0,0 +1,60 @@
/**
* Copyright 2020 Microsoft Corporation. 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 fs from 'fs';
import { PNG } from 'playwright-core/lib/utilsBundle';
import { androidTest as test, expect } from './androidTest';
test('androidDevice.shell', async function({ androidDevice }) {
const output = await androidDevice.shell('echo 123');
expect(output.toString()).toBe('123\n');
});
test('androidDevice.open', async function({ androidDevice }) {
const socket = await androidDevice.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');
const closedPromise = new Promise<void>(resolve => socket.on('close', resolve));
await socket.close();
await closedPromise;
});
test('androidDevice.screenshot', async function({ androidDevice }, testInfo) {
const path = testInfo.outputPath('screenshot.png');
const result = await androidDevice.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);
});
test('androidDevice.push', async function({ androidDevice }) {
try {
await androidDevice.push(Buffer.from('hello world'), '/data/local/tmp/hello-world');
const data = await androidDevice.shell('cat /data/local/tmp/hello-world');
expect(data).toEqual(Buffer.from('hello world'));
} finally {
await androidDevice.shell('rm /data/local/tmp/hello-world');
}
});
test('androidDevice.fill', async function({ androidDevice }) {
await androidDevice.shell('am start org.chromium.webview_shell/.WebViewBrowserActivity');
await androidDevice.fill({ res: 'org.chromium.webview_shell:id/url_field' }, 'Hello', { timeout: test.info().timeout });
expect((await androidDevice.info({ res: 'org.chromium.webview_shell:id/url_field' })).text).toBe('Hello');
});

View File

@@ -0,0 +1,168 @@
/**
* Copyright 2020 Microsoft Corporation. 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 { WebSocket, WebSocketServer, type AddressInfo } from 'ws';
import { androidTest as test, expect } from './androidTest';
import { kTargetClosedErrorMessage } from '../config/errors';
// Force a separate worker to avoid messing up with `androidDevice` fixture.
test.use({ launchOptions: [async ({ launchOptions }, use) => use(launchOptions), { scope: 'worker' }] });
test('android.launchServer should connect to a device', async ({ playwright }) => {
const browserServer = await playwright._android.launchServer();
const device = await playwright._android.connect(browserServer.wsEndpoint());
const output = await device.shell('echo 123');
expect(output.toString()).toBe('123\n');
await device.close();
await browserServer.close();
});
test('android.launchServer should work with host', async ({ playwright }) => {
const host = '0.0.0.0';
const browserServer = await playwright._android.launchServer({ host });
expect(browserServer.wsEndpoint()).toContain(String(host));
const device = await playwright._android.connect(browserServer.wsEndpoint());
const output = await device.shell('echo 123');
expect(output.toString()).toBe('123\n');
await device.close();
await browserServer.close();
});
test('android.launchServer should handle close event correctly', async ({ playwright }) => {
const receivedEvents: string[] = [];
const browserServer = await playwright._android.launchServer();
const device = await playwright._android.connect(browserServer.wsEndpoint());
device.on('close', () => receivedEvents.push('device'));
browserServer.on('close', () => receivedEvents.push('browserServer'));
{
const waitForDeviceClose = new Promise(f => device.on('close', f));
await device.close();
await waitForDeviceClose;
}
expect(receivedEvents).toEqual(['device']);
await device.close();
expect(receivedEvents).toEqual(['device']);
await browserServer.close();
expect(receivedEvents).toEqual(['device', 'browserServer']);
await browserServer.close();
expect(receivedEvents).toEqual(['device', 'browserServer']);
await device.close();
expect(receivedEvents).toEqual(['device', 'browserServer']);
});
test('android.launchServer should be able to reconnect to a device', async ({ playwright }) => {
const browserServer = await playwright._android.launchServer();
try {
{
const device = await playwright._android.connect(browserServer.wsEndpoint());
await device.push(Buffer.from('hello world'), '/data/local/tmp/hello-world');
await device.close();
}
{
const device = await playwright._android.connect(browserServer.wsEndpoint());
const data = await device.shell('cat /data/local/tmp/hello-world');
expect(data).toEqual(Buffer.from('hello world'));
await device.close();
}
} finally {
// Cleanup
const device = await playwright._android.connect(browserServer.wsEndpoint());
await device.shell('rm /data/local/tmp/hello-world');
await device.close();
await browserServer.close();
}
});
test('android.launchServer should throw if there is no device with a specified serial number', async ({ playwright }) => {
await expect(playwright._android.launchServer({
deviceSerialNumber: 'does-not-exist',
})).rejects.toThrow(`No device with serial number 'does-not-exist'`);
});
test('android.launchServer should not allow multiple connections', async ({ playwright }) => {
const browserServer = await playwright._android.launchServer();
try {
await playwright._android.connect(browserServer.wsEndpoint());
await expect(playwright._android.connect(browserServer.wsEndpoint(), { timeout: 2_000 })).rejects.toThrow('android.connect: Timeout 2000ms exceeded');
} finally {
await browserServer.close();
}
});
test('android.launchServer BrowserServer.close() will disconnect the device', async ({ playwright }) => {
const browserServer = await playwright._android.launchServer();
try {
const device = await playwright._android.connect(browserServer.wsEndpoint());
await browserServer.close();
await expect(device.shell('echo 123')).rejects.toThrow('androidDevice.shell: ' + kTargetClosedErrorMessage);
} finally {
await browserServer.close();
}
});
test('android.launchServer BrowserServer.kill() will disconnect the device', async ({ playwright }) => {
const browserServer = await playwright._android.launchServer();
try {
const device = await playwright._android.connect(browserServer.wsEndpoint());
await browserServer.kill();
await expect(device.shell('echo 123')).rejects.toThrow('androidDevice.shell: ' + kTargetClosedErrorMessage);
} finally {
await browserServer.close();
}
});
test('android.launchServer should terminate WS connection when device gets disconnected', async ({ playwright }) => {
const browserServer = await playwright._android.launchServer();
const forwardingServer = new WebSocketServer({ port: 0, path: '/connect' });
let receivedConnection: WebSocket | undefined;
forwardingServer.on('connection', connection => {
// Pause the connection until we establish the actual connection to the browser server.
// Someone is using non-existing api
(connection as any).pause();
receivedConnection = connection;
const actualConnection = new WebSocket(browserServer.wsEndpoint());
// We need to wait for the actual connection to be established before resuming
actualConnection.on('open', () => (connection as any).resume());
actualConnection.on('message', message => connection.send(message));
connection.on('message', message => actualConnection.send(message));
connection.on('close', () => actualConnection.close());
actualConnection.on('close', () => connection.close());
});
try {
const device = await playwright._android.connect(`ws://localhost:${(forwardingServer.address() as AddressInfo).port}/connect`);
expect((await device.shell('echo 123')).toString()).toBe('123\n');
expect(receivedConnection!.readyState).toBe(WebSocket.OPEN);
const waitToClose = new Promise(f => receivedConnection!.on('close', f));
await device.close();
await waitToClose;
expect(receivedConnection!.readyState).toBe(WebSocket.CLOSED);
} finally {
await browserServer.close();
await new Promise(f => forwardingServer.close(f));
}
});
test('android.launchServer should be able to launch browser', { annotation: { type: 'issue', description: 'https://github.com/microsoft/playwright/issues/36911' } }, async ({ playwright }) => {
const browserServer = await playwright._android.launchServer();
const device = await playwright._android.connect(browserServer.wsEndpoint());
const context = await device.launchBrowser();
const [page] = context.pages();
await page.goto('data:text/html,<title>Hello world!</title>');
expect(await page.title()).toBe('Hello world!');
await context.close();
await device.close();
await browserServer.close();
});

View File

@@ -0,0 +1,78 @@
/**
* 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 { config as loadEnv } from 'dotenv';
loadEnv({ path: path.join(__dirname, '..', '..', '.env') });
process.env.PWTEST_UNDER_TEST = '1';
import type { Config, PlaywrightTestOptions, PlaywrightWorkerOptions } from '@playwright/test';
import * as path from 'path';
import type { ServerWorkerOptions } from '../config/serverFixtures';
process.env.PWPAGE_IMPL = 'android';
const outputDir = path.join(__dirname, '..', '..', 'test-results');
const testDir = path.join(__dirname, '..');
const config: Config<ServerWorkerOptions & PlaywrightWorkerOptions & PlaywrightTestOptions> = {
testDir,
outputDir,
expect: {
timeout: 10000,
},
timeout: 120000,
globalTimeout: 7200000,
workers: 1,
forbidOnly: !!process.env.CI,
retries: process.env.CI ? 1 : 0,
reporter: process.env.CI ? [
['dot'],
['json', { outputFile: path.join(outputDir, 'report.json') }],
] : 'line',
projects: [],
};
const metadata = {
platform: 'Android',
headless: 'headless',
browserName: 'chromium',
channel: 'chrome',
mode: 'default',
video: false,
};
config.projects!.push({
name: 'android-native',
use: {
loopback: '10.0.2.2',
browserName: 'chromium',
},
snapshotPathTemplate: '{testDir}/{testFileDir}/{testFileName}-snapshots/{arg}-android{ext}',
testDir: path.join(testDir, 'android'),
metadata,
});
config.projects!.push({
name: 'android-page',
use: {
loopback: '10.0.2.2',
browserName: 'chromium',
},
snapshotPathTemplate: '{testDir}/{testFileDir}/{testFileName}-snapshots/{arg}-android{ext}',
testDir: path.join(testDir, 'page'),
metadata,
});
export default config;

View File

@@ -0,0 +1,106 @@
/**
* Copyright 2020 Microsoft Corporation. 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 { androidTest as test, expect } from './androidTest';
test('androidDevice.webView', async function({ androidDevice }) {
expect(androidDevice.webViews().length).toBe(0);
await androidDevice.shell('am start org.chromium.webview_shell/.WebViewBrowserActivity');
const webview = await androidDevice.webView({ pkg: 'org.chromium.webview_shell' });
expect(webview.pkg()).toBe('org.chromium.webview_shell');
expect(androidDevice.webViews().length).toBe(1);
});
test('webView.page', async function({ androidDevice }) {
expect(androidDevice.webViews().length).toBe(0);
await androidDevice.shell('am start org.chromium.webview_shell/.WebViewBrowserActivity');
const webview = await androidDevice.webView({ pkg: 'org.chromium.webview_shell' });
const page = await webview.page();
expect(page.url()).toBe('about:blank');
});
test('should navigate page internally', async function({ androidDevice }) {
expect(androidDevice.webViews().length).toBe(0);
await androidDevice.shell('am start org.chromium.webview_shell/.WebViewBrowserActivity');
const webview = await androidDevice.webView({ pkg: 'org.chromium.webview_shell' });
const page = await webview.page();
await page.goto('data:text/html,<title>Hello world!</title>');
expect(await page.title()).toBe('Hello world!');
});
test('should navigate page externally', async function({ androidDevice }) {
expect(androidDevice.webViews().length).toBe(0);
await androidDevice.shell('am start org.chromium.webview_shell/.WebViewBrowserActivity');
const webview = await androidDevice.webView({ pkg: 'org.chromium.webview_shell' });
const page = await webview.page();
await androidDevice.fill({ res: 'org.chromium.webview_shell:id/url_field' }, 'data:text/html,<title>Hello world!</title>', { timeout: test.info().timeout });
await Promise.all([
page.waitForNavigation(),
androidDevice.press({ res: 'org.chromium.webview_shell:id/url_field' }, 'Enter')
]);
expect(await page.title()).toBe('Hello world!');
});
test('select webview from socketName', async function({ androidDevice }) {
const context = await androidDevice.launchBrowser();
const newPage = await context.newPage();
await newPage.goto('about:blank');
const webview = await androidDevice.webView({ socketName: 'webview_devtools_remote_playwright_test' });
expect(webview.pkg()).toBe('');
expect(webview.pid()).toBe(-1);
const page = await webview.page();
expect(page.url()).toBe('about:blank');
await newPage.close();
await context.close();
});
// Requires a newer WebView version with
// https://chromium-review.googlesource.com/c/chromium/src/+/6411892
test.fail('should be able to receive webView cookies', {
annotation: {
type: 'issue',
description: 'https://github.com/microsoft/playwright/issues/35392',
}
}, async function({ androidDevice, server }) {
expect(androidDevice.webViews().length).toBe(0);
server.setRoute('/cookies', (req, res) => {
res.setHeader('Set-Cookie', 'cookie1=value1; Path=/; HttpOnly');
res.setHeader('Content-Type', 'text/html');
res.end('<html><body>hello world</body></html>');
});
await androidDevice.shell('am start org.chromium.webview_shell/.WebViewBrowserActivity');
const webview = await androidDevice.webView({ pkg: 'org.chromium.webview_shell' });
const page = await webview.page();
await page.goto(server.CROSS_PROCESS_PREFIX + '/cookies');
const cookies = await page.context().cookies();
expect(cookies.length).toBe(1);
expect(cookies).toEqual([
{
name: 'cookie1',
value: 'value1',
domain: new URL(server.CROSS_PROCESS_PREFIX).hostname,
path: '/',
expires: -1,
httpOnly: true,
secure: false,
sameSite: 'Lax'
}
]);
await page.context().clearCookies();
});