feat(android): allow getting webviews by socket name (#13248)
This commit is contained in:
parent
d91349f22a
commit
d65263f151
|
|
@ -358,7 +358,8 @@ This method waits until [AndroidWebView] matching the [`option: selector`] is op
|
||||||
|
|
||||||
### param: AndroidDevice.webView.selector
|
### param: AndroidDevice.webView.selector
|
||||||
- `selector` <[Object]>
|
- `selector` <[Object]>
|
||||||
- `pkg` <[string]> Package identifier.
|
- `pkg` ?<[string]> Optional Package identifier.
|
||||||
|
- `socketName` ?<[string]> Optional webview socket name.
|
||||||
|
|
||||||
### option: AndroidDevice.webView.timeout = %%-android-timeout-%%
|
### option: AndroidDevice.webView.timeout = %%-android-timeout-%%
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -20,4 +20,4 @@ WebView process PID.
|
||||||
## method: AndroidWebView.pkg
|
## method: AndroidWebView.pkg
|
||||||
- returns: <[string]>
|
- returns: <[string]>
|
||||||
|
|
||||||
WebView package identifier.
|
WebView package identifier.
|
||||||
|
|
@ -55,7 +55,7 @@ export class Android extends ChannelOwner<channels.AndroidChannel> implements ap
|
||||||
|
|
||||||
export class AndroidDevice extends ChannelOwner<channels.AndroidDeviceChannel> implements api.AndroidDevice {
|
export class AndroidDevice extends ChannelOwner<channels.AndroidDeviceChannel> implements api.AndroidDevice {
|
||||||
readonly _timeoutSettings: TimeoutSettings;
|
readonly _timeoutSettings: TimeoutSettings;
|
||||||
private _webViews = new Map<number, AndroidWebView>();
|
private _webViews = new Map<string, AndroidWebView>();
|
||||||
|
|
||||||
static from(androidDevice: channels.AndroidDeviceChannel): AndroidDevice {
|
static from(androidDevice: channels.AndroidDeviceChannel): AndroidDevice {
|
||||||
return (androidDevice as any)._object;
|
return (androidDevice as any)._object;
|
||||||
|
|
@ -68,18 +68,18 @@ export class AndroidDevice extends ChannelOwner<channels.AndroidDeviceChannel> i
|
||||||
this.input = new AndroidInput(this);
|
this.input = new AndroidInput(this);
|
||||||
this._timeoutSettings = new TimeoutSettings((parent as Android)._timeoutSettings);
|
this._timeoutSettings = new TimeoutSettings((parent as Android)._timeoutSettings);
|
||||||
this._channel.on('webViewAdded', ({ webView }) => this._onWebViewAdded(webView));
|
this._channel.on('webViewAdded', ({ webView }) => this._onWebViewAdded(webView));
|
||||||
this._channel.on('webViewRemoved', ({ pid }) => this._onWebViewRemoved(pid));
|
this._channel.on('webViewRemoved', ({ socketName }) => this._onWebViewRemoved(socketName));
|
||||||
}
|
}
|
||||||
|
|
||||||
private _onWebViewAdded(webView: channels.AndroidWebView) {
|
private _onWebViewAdded(webView: channels.AndroidWebView) {
|
||||||
const view = new AndroidWebView(this, webView);
|
const view = new AndroidWebView(this, webView);
|
||||||
this._webViews.set(webView.pid, view);
|
this._webViews.set(webView.socketName, view);
|
||||||
this.emit(Events.AndroidDevice.WebView, view);
|
this.emit(Events.AndroidDevice.WebView, view);
|
||||||
}
|
}
|
||||||
|
|
||||||
private _onWebViewRemoved(pid: number) {
|
private _onWebViewRemoved(socketName: string) {
|
||||||
const view = this._webViews.get(pid);
|
const view = this._webViews.get(socketName);
|
||||||
this._webViews.delete(pid);
|
this._webViews.delete(socketName);
|
||||||
if (view)
|
if (view)
|
||||||
view.emit(Events.AndroidWebView.Close);
|
view.emit(Events.AndroidWebView.Close);
|
||||||
}
|
}
|
||||||
|
|
@ -101,14 +101,18 @@ export class AndroidDevice extends ChannelOwner<channels.AndroidDeviceChannel> i
|
||||||
return [...this._webViews.values()];
|
return [...this._webViews.values()];
|
||||||
}
|
}
|
||||||
|
|
||||||
async webView(selector: { pkg: string }, options?: types.TimeoutOptions): Promise<AndroidWebView> {
|
async webView(selector: { pkg?: string; socketName?: string; }, options?: types.TimeoutOptions): Promise<AndroidWebView> {
|
||||||
const webView = [...this._webViews.values()].find(v => v.pkg() === selector.pkg);
|
const predicate = (v: AndroidWebView) => {
|
||||||
|
if (selector.pkg)
|
||||||
|
return v.pkg() === selector.pkg;
|
||||||
|
if (selector.socketName)
|
||||||
|
return v._socketName() === selector.socketName;
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
const webView = [...this._webViews.values()].find(predicate);
|
||||||
if (webView)
|
if (webView)
|
||||||
return webView;
|
return webView;
|
||||||
return this.waitForEvent('webview', {
|
return this.waitForEvent('webview', { ...options, predicate });
|
||||||
...options,
|
|
||||||
predicate: (view: AndroidWebView) => view.pkg() === selector.pkg
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async wait(selector: api.AndroidSelector, options?: { state?: 'gone' } & types.TimeoutOptions) {
|
async wait(selector: api.AndroidSelector, options?: { state?: 'gone' } & types.TimeoutOptions) {
|
||||||
|
|
@ -334,6 +338,10 @@ export class AndroidWebView extends EventEmitter implements api.AndroidWebView {
|
||||||
return this._data.pkg;
|
return this._data.pkg;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
_socketName(): string {
|
||||||
|
return this._data.socketName;
|
||||||
|
}
|
||||||
|
|
||||||
async page(): Promise<Page> {
|
async page(): Promise<Page> {
|
||||||
if (!this._pagePromise)
|
if (!this._pagePromise)
|
||||||
this._pagePromise = this._fetchPage();
|
this._pagePromise = this._fetchPage();
|
||||||
|
|
@ -341,7 +349,7 @@ export class AndroidWebView extends EventEmitter implements api.AndroidWebView {
|
||||||
}
|
}
|
||||||
|
|
||||||
private async _fetchPage(): Promise<Page> {
|
private async _fetchPage(): Promise<Page> {
|
||||||
const { context } = await this._device._channel.connectToWebView({ pid: this._data.pid });
|
const { context } = await this._device._channel.connectToWebView({ socketName: this._data.socketName });
|
||||||
return BrowserContext.from(context).pages()[0];
|
return BrowserContext.from(context).pages()[0];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -3790,7 +3790,7 @@ export type AndroidDeviceWebViewAddedEvent = {
|
||||||
webView: AndroidWebView,
|
webView: AndroidWebView,
|
||||||
};
|
};
|
||||||
export type AndroidDeviceWebViewRemovedEvent = {
|
export type AndroidDeviceWebViewRemovedEvent = {
|
||||||
pid: number,
|
socketName: string,
|
||||||
};
|
};
|
||||||
export type AndroidDeviceWaitParams = {
|
export type AndroidDeviceWaitParams = {
|
||||||
selector: AndroidSelector,
|
selector: AndroidSelector,
|
||||||
|
|
@ -4107,7 +4107,7 @@ export type AndroidDeviceSetDefaultTimeoutNoReplyOptions = {
|
||||||
};
|
};
|
||||||
export type AndroidDeviceSetDefaultTimeoutNoReplyResult = void;
|
export type AndroidDeviceSetDefaultTimeoutNoReplyResult = void;
|
||||||
export type AndroidDeviceConnectToWebViewParams = {
|
export type AndroidDeviceConnectToWebViewParams = {
|
||||||
pid: number,
|
socketName: string,
|
||||||
};
|
};
|
||||||
export type AndroidDeviceConnectToWebViewOptions = {
|
export type AndroidDeviceConnectToWebViewOptions = {
|
||||||
|
|
||||||
|
|
@ -4127,6 +4127,7 @@ export interface AndroidDeviceEvents {
|
||||||
export type AndroidWebView = {
|
export type AndroidWebView = {
|
||||||
pid: number,
|
pid: number,
|
||||||
pkg: string,
|
pkg: string,
|
||||||
|
socketName: string,
|
||||||
};
|
};
|
||||||
|
|
||||||
export type AndroidSelector = {
|
export type AndroidSelector = {
|
||||||
|
|
|
||||||
|
|
@ -3058,7 +3058,7 @@ AndroidDevice:
|
||||||
|
|
||||||
connectToWebView:
|
connectToWebView:
|
||||||
parameters:
|
parameters:
|
||||||
pid: number
|
socketName: string
|
||||||
returns:
|
returns:
|
||||||
context: BrowserContext
|
context: BrowserContext
|
||||||
|
|
||||||
|
|
@ -3071,7 +3071,7 @@ AndroidDevice:
|
||||||
|
|
||||||
webViewRemoved:
|
webViewRemoved:
|
||||||
parameters:
|
parameters:
|
||||||
pid: number
|
socketName: string
|
||||||
|
|
||||||
|
|
||||||
AndroidWebView:
|
AndroidWebView:
|
||||||
|
|
@ -3079,6 +3079,7 @@ AndroidWebView:
|
||||||
properties:
|
properties:
|
||||||
pid: number
|
pid: number
|
||||||
pkg: string
|
pkg: string
|
||||||
|
socketName: string
|
||||||
|
|
||||||
|
|
||||||
AndroidSelector:
|
AndroidSelector:
|
||||||
|
|
|
||||||
|
|
@ -1472,12 +1472,13 @@ export function createScheme(tChannel: (name: string) => Validator): Scheme {
|
||||||
timeout: tNumber,
|
timeout: tNumber,
|
||||||
});
|
});
|
||||||
scheme.AndroidDeviceConnectToWebViewParams = tObject({
|
scheme.AndroidDeviceConnectToWebViewParams = tObject({
|
||||||
pid: tNumber,
|
socketName: tString,
|
||||||
});
|
});
|
||||||
scheme.AndroidDeviceCloseParams = tOptional(tObject({}));
|
scheme.AndroidDeviceCloseParams = tOptional(tObject({}));
|
||||||
scheme.AndroidWebView = tObject({
|
scheme.AndroidWebView = tObject({
|
||||||
pid: tNumber,
|
pid: tNumber,
|
||||||
pkg: tString,
|
pkg: tString,
|
||||||
|
socketName: tString,
|
||||||
});
|
});
|
||||||
scheme.AndroidSelector = tObject({
|
scheme.AndroidSelector = tObject({
|
||||||
checkable: tOptional(tBoolean),
|
checkable: tOptional(tBoolean),
|
||||||
|
|
|
||||||
|
|
@ -22,7 +22,7 @@ import os from 'os';
|
||||||
import path from 'path';
|
import path from 'path';
|
||||||
import type * as stream from 'stream';
|
import type * as stream from 'stream';
|
||||||
import * as ws from 'ws';
|
import * as ws from 'ws';
|
||||||
import { createGuid, makeWaitForNextTask } from '../../utils';
|
import { createGuid, makeWaitForNextTask, isUnderTest } from '../../utils';
|
||||||
import { removeFolders } from '../../utils/fileUtils';
|
import { removeFolders } from '../../utils/fileUtils';
|
||||||
import type { BrowserOptions, BrowserProcess, PlaywrightOptions } from '../browser';
|
import type { BrowserOptions, BrowserProcess, PlaywrightOptions } from '../browser';
|
||||||
import type { BrowserContext } from '../browserContext';
|
import type { BrowserContext } from '../browserContext';
|
||||||
|
|
@ -107,7 +107,7 @@ export class AndroidDevice extends SdkObject {
|
||||||
private _callbacks = new Map<number, { fulfill: (result: any) => void, reject: (error: Error) => void }>();
|
private _callbacks = new Map<number, { fulfill: (result: any) => void, reject: (error: Error) => void }>();
|
||||||
private _pollingWebViews: NodeJS.Timeout | undefined;
|
private _pollingWebViews: NodeJS.Timeout | undefined;
|
||||||
readonly _timeoutSettings: TimeoutSettings;
|
readonly _timeoutSettings: TimeoutSettings;
|
||||||
private _webViews = new Map<number, AndroidWebView>();
|
private _webViews = new Map<string, AndroidWebView>();
|
||||||
|
|
||||||
static Events = {
|
static Events = {
|
||||||
WebViewAdded: 'webViewAdded',
|
WebViewAdded: 'webViewAdded',
|
||||||
|
|
@ -247,8 +247,7 @@ export class AndroidDevice extends SdkObject {
|
||||||
async launchBrowser(pkg: string = 'com.android.chrome', options: types.BrowserContextOptions): Promise<BrowserContext> {
|
async launchBrowser(pkg: string = 'com.android.chrome', options: types.BrowserContextOptions): Promise<BrowserContext> {
|
||||||
debug('pw:android')('Force-stopping', pkg);
|
debug('pw:android')('Force-stopping', pkg);
|
||||||
await this._backend.runCommand(`shell:am force-stop ${pkg}`);
|
await this._backend.runCommand(`shell:am force-stop ${pkg}`);
|
||||||
|
const socketName = isUnderTest() ? 'webview_devtools_remote_playwright_test' : ('playwright-' + createGuid());
|
||||||
const socketName = 'playwright-' + createGuid();
|
|
||||||
const commandLine = `_ --disable-fre --no-default-browser-check --no-first-run --remote-debugging-socket-name=${socketName}`;
|
const commandLine = `_ --disable-fre --no-default-browser-check --no-first-run --remote-debugging-socket-name=${socketName}`;
|
||||||
debug('pw:android')('Starting', pkg, commandLine);
|
debug('pw:android')('Starting', pkg, commandLine);
|
||||||
await this._backend.runCommand(`shell:echo "${commandLine}" > /data/local/tmp/chrome-command-line`);
|
await this._backend.runCommand(`shell:echo "${commandLine}" > /data/local/tmp/chrome-command-line`);
|
||||||
|
|
@ -256,11 +255,11 @@ export class AndroidDevice extends SdkObject {
|
||||||
return await this._connectToBrowser(socketName, options);
|
return await this._connectToBrowser(socketName, options);
|
||||||
}
|
}
|
||||||
|
|
||||||
async connectToWebView(pid: number): Promise<BrowserContext> {
|
async connectToWebView(socketName: string): Promise<BrowserContext> {
|
||||||
const webView = this._webViews.get(pid);
|
const webView = this._webViews.get(socketName);
|
||||||
if (!webView)
|
if (!webView)
|
||||||
throw new Error('WebView has been closed');
|
throw new Error('WebView has been closed');
|
||||||
return await this._connectToBrowser(`webview_devtools_remote_${pid}`);
|
return await this._connectToBrowser(socketName);
|
||||||
}
|
}
|
||||||
|
|
||||||
private async _connectToBrowser(socketName: string, options: types.BrowserContextOptions = {}): Promise<BrowserContext> {
|
private async _connectToBrowser(socketName: string, options: types.BrowserContextOptions = {}): Promise<BrowserContext> {
|
||||||
|
|
@ -345,47 +344,59 @@ export class AndroidDevice extends SdkObject {
|
||||||
}
|
}
|
||||||
|
|
||||||
private async _refreshWebViews() {
|
private async _refreshWebViews() {
|
||||||
|
// possible socketName, eg: webview_devtools_remote_32327, webview_devtools_remote_32327_zeus, webview_devtools_remote_zeus
|
||||||
const sockets = (await this._backend.runCommand(`shell:cat /proc/net/unix | grep webview_devtools_remote`)).toString().split('\n');
|
const sockets = (await this._backend.runCommand(`shell:cat /proc/net/unix | grep webview_devtools_remote`)).toString().split('\n');
|
||||||
if (this._isClosed)
|
if (this._isClosed)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
const newPids = new Set<number>();
|
const socketNames = new Set<string>();
|
||||||
for (const line of sockets) {
|
for (const line of sockets) {
|
||||||
const match = line.match(/[^@]+@webview_devtools_remote_(\d+)/);
|
const matchSocketName = line.match(/[^@]+@(.*?webview_devtools_remote_?.*)/);
|
||||||
if (!match)
|
if (!matchSocketName)
|
||||||
continue;
|
|
||||||
const pid = +match[1];
|
|
||||||
newPids.add(pid);
|
|
||||||
}
|
|
||||||
for (const pid of newPids) {
|
|
||||||
if (this._webViews.has(pid))
|
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
const procs = (await this._backend.runCommand(`shell:ps -A | grep ${pid}`)).toString().split('\n');
|
const socketName = matchSocketName[1];
|
||||||
|
socketNames.add(socketName);
|
||||||
|
if (this._webViews.has(socketName))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
// possible line: 0000000000000000: 00000002 00000000 00010000 0001 01 5841881 @webview_devtools_remote_zeus
|
||||||
|
// the result: match[1] = ''
|
||||||
|
const match = line.match(/[^@]+@.*?webview_devtools_remote_?(\d*)/);
|
||||||
|
let pid = -1;
|
||||||
|
if (match && match[1])
|
||||||
|
pid = +match[1];
|
||||||
|
|
||||||
|
const pkg = await this._extractPkg(pid);
|
||||||
if (this._isClosed)
|
if (this._isClosed)
|
||||||
return;
|
return;
|
||||||
let pkg = '';
|
|
||||||
for (const proc of procs) {
|
const webView = { pid, pkg, socketName };
|
||||||
const match = proc.match(/[^\s]+\s+(\d+).*$/);
|
this._webViews.set(socketName, webView);
|
||||||
if (!match)
|
|
||||||
continue;
|
|
||||||
const p = match[1];
|
|
||||||
if (+p !== pid)
|
|
||||||
continue;
|
|
||||||
pkg = proc.substring(proc.lastIndexOf(' ') + 1);
|
|
||||||
}
|
|
||||||
const webView = { pid, pkg };
|
|
||||||
this._webViews.set(pid, webView);
|
|
||||||
this.emit(AndroidDevice.Events.WebViewAdded, webView);
|
this.emit(AndroidDevice.Events.WebViewAdded, webView);
|
||||||
}
|
}
|
||||||
|
|
||||||
for (const p of this._webViews.keys()) {
|
for (const p of this._webViews.keys()) {
|
||||||
if (!newPids.has(p)) {
|
if (!socketNames.has(p)) {
|
||||||
this._webViews.delete(p);
|
this._webViews.delete(p);
|
||||||
this.emit(AndroidDevice.Events.WebViewRemoved, p);
|
this.emit(AndroidDevice.Events.WebViewRemoved, p);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private async _extractPkg(pid: number) {
|
||||||
|
let pkg = '';
|
||||||
|
if (pid === -1)
|
||||||
|
return pkg;
|
||||||
|
|
||||||
|
const procs = (await this._backend.runCommand(`shell:ps -A | grep ${pid}`)).toString().split('\n');
|
||||||
|
for (const proc of procs) {
|
||||||
|
const match = proc.match(/[^\s]+\s+(\d+).*$/);
|
||||||
|
if (!match)
|
||||||
|
continue;
|
||||||
|
pkg = proc.substring(proc.lastIndexOf(' ') + 1);
|
||||||
|
}
|
||||||
|
return pkg;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class AndroidBrowser extends EventEmitter {
|
class AndroidBrowser extends EventEmitter {
|
||||||
|
|
@ -465,3 +476,5 @@ class ClankBrowserProcess implements BrowserProcess {
|
||||||
await this._browser.close();
|
await this._browser.close();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -57,7 +57,7 @@ export class AndroidDeviceDispatcher extends Dispatcher<AndroidDevice, channels.
|
||||||
for (const webView of device.webViews())
|
for (const webView of device.webViews())
|
||||||
this._dispatchEvent('webViewAdded', { webView });
|
this._dispatchEvent('webViewAdded', { webView });
|
||||||
device.on(AndroidDevice.Events.WebViewAdded, webView => this._dispatchEvent('webViewAdded', { webView }));
|
device.on(AndroidDevice.Events.WebViewAdded, webView => this._dispatchEvent('webViewAdded', { webView }));
|
||||||
device.on(AndroidDevice.Events.WebViewRemoved, pid => this._dispatchEvent('webViewRemoved', { pid }));
|
device.on(AndroidDevice.Events.WebViewRemoved, socketName => this._dispatchEvent('webViewRemoved', { socketName }));
|
||||||
}
|
}
|
||||||
|
|
||||||
async wait(params: channels.AndroidDeviceWaitParams) {
|
async wait(params: channels.AndroidDeviceWaitParams) {
|
||||||
|
|
@ -170,7 +170,7 @@ export class AndroidDeviceDispatcher extends Dispatcher<AndroidDevice, channels.
|
||||||
}
|
}
|
||||||
|
|
||||||
async connectToWebView(params: channels.AndroidDeviceConnectToWebViewParams): Promise<channels.AndroidDeviceConnectToWebViewResult> {
|
async connectToWebView(params: channels.AndroidDeviceConnectToWebViewParams): Promise<channels.AndroidDeviceConnectToWebViewResult> {
|
||||||
return { context: new BrowserContextDispatcher(this._scope, await this._object.connectToWebView(params.pid)) };
|
return { context: new BrowserContextDispatcher(this._scope, await this._object.connectToWebView(params.socketName)) };
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
9
packages/playwright-core/types/types.d.ts
vendored
9
packages/playwright-core/types/types.d.ts
vendored
|
|
@ -11727,9 +11727,14 @@ export interface AndroidDevice {
|
||||||
*/
|
*/
|
||||||
webView(selector: {
|
webView(selector: {
|
||||||
/**
|
/**
|
||||||
* Package identifier.
|
* Optional Package identifier.
|
||||||
*/
|
*/
|
||||||
pkg: string;
|
pkg?: string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Optional webview socket name.
|
||||||
|
*/
|
||||||
|
socketName?: string;
|
||||||
}, options?: {
|
}, options?: {
|
||||||
/**
|
/**
|
||||||
* Maximum time in milliseconds, defaults to 30 seconds, pass `0` to disable timeout. The default value can be changed by
|
* Maximum time in milliseconds, defaults to 30 seconds, pass `0` to disable timeout. The default value can be changed by
|
||||||
|
|
|
||||||
|
|
@ -18,6 +18,7 @@ import { androidTest as test, expect } from './androidTest';
|
||||||
|
|
||||||
test.afterEach(async ({ androidDevice }) => {
|
test.afterEach(async ({ androidDevice }) => {
|
||||||
await androidDevice.shell('am force-stop org.chromium.webview_shell');
|
await androidDevice.shell('am force-stop org.chromium.webview_shell');
|
||||||
|
await androidDevice.shell('am force-stop com.android.chrome');
|
||||||
});
|
});
|
||||||
|
|
||||||
test('androidDevice.webView', async function({ androidDevice }) {
|
test('androidDevice.webView', async function({ androidDevice }) {
|
||||||
|
|
@ -61,3 +62,19 @@ test('should navigate page externally', async function({ androidDevice }) {
|
||||||
]);
|
]);
|
||||||
expect(await page.title()).toBe('Hello world!');
|
expect(await page.title()).toBe('Hello world!');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('select webview from socketName', async function({ androidDevice }) {
|
||||||
|
test.slow();
|
||||||
|
const context = await androidDevice.launchBrowser();
|
||||||
|
const newPage = await context.newPage();
|
||||||
|
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();
|
||||||
|
});
|
||||||
Loading…
Reference in a new issue