chore: make targets chromium feature (#137)

This commit is contained in:
Pavel Feldman 2019-12-04 16:12:43 -08:00 committed by GitHub
parent 3305363f2a
commit e1c4eaae28
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
32 changed files with 302 additions and 425 deletions

View File

@ -28,9 +28,6 @@
* [browserFetcher.revisionInfo(revision)](#browserfetcherrevisioninforevision)
- [class: Browser](#class-browser)
* [event: 'disconnected'](#event-disconnected)
* [event: 'targetchanged'](#event-targetchanged)
* [event: 'targetcreated'](#event-targetcreated)
* [event: 'targetdestroyed'](#event-targetdestroyed)
* [browser.browserContexts()](#browserbrowsercontexts)
* [browser.chromium](#browserchromium)
* [browser.close()](#browserclose)
@ -41,14 +38,9 @@
* [browser.newPage()](#browsernewpage)
* [browser.pages()](#browserpages)
* [browser.process()](#browserprocess)
* [browser.targets()](#browsertargets)
* [browser.userAgent()](#browseruseragent)
* [browser.version()](#browserversion)
* [browser.waitForTarget(predicate[, options])](#browserwaitfortargetpredicate-options)
- [class: BrowserContext](#class-browsercontext)
* [event: 'targetchanged'](#event-targetchanged-1)
* [event: 'targetcreated'](#event-targetcreated-1)
* [event: 'targetdestroyed'](#event-targetdestroyed-1)
* [browserContext.browser()](#browsercontextbrowser)
* [browserContext.clearCookies()](#browsercontextclearcookies)
* [browserContext.close()](#browsercontextclose)
@ -58,8 +50,6 @@
* [browserContext.pages()](#browsercontextpages)
* [browserContext.permissions](#browsercontextpermissions)
* [browserContext.setCookies(cookies)](#browsercontextsetcookiescookies)
* [browserContext.targets()](#browsercontexttargets)
* [browserContext.waitForTarget(predicate[, options])](#browsercontextwaitfortargetpredicate-options)
- [class: Overrides](#class-overrides)
* [overrides.setGeolocation(options)](#overridessetgeolocationoptions)
* [overrides.setTimezone(timezoneId)](#overridessettimezonetimezoneid)
@ -129,7 +119,6 @@
* [page.setJavaScriptEnabled(enabled)](#pagesetjavascriptenabledenabled)
* [page.setUserAgent(userAgent)](#pagesetuseragentuseragent)
* [page.setViewport(viewport)](#pagesetviewportviewport)
* [page.target()](#pagetarget)
* [page.title()](#pagetitle)
* [page.tripleclick(selector[, options])](#pagetripleclickselector-options)
* [page.type(selector, text[, options])](#pagetypeselector-text-options)
@ -171,11 +160,16 @@
- [class: PDF](#class-pdf)
* [pdf.generate([options])](#pdfgenerateoptions)
- [class: Chromium](#class-chromium)
* [chromium.createBrowserCDPSession()](#chromiumcreatebrowsercdpsession)
* [chromium.createCDPSession(target)](#chromiumcreatecdpsessiontarget)
* [event: 'targetchanged'](#event-targetchanged)
* [event: 'targetcreated'](#event-targetcreated)
* [event: 'targetdestroyed'](#event-targetdestroyed)
* [chromium.browserTarget()](#chromiumbrowsertarget)
* [chromium.pageTarget(page)](#chromiumpagetargetpage)
* [chromium.serviceWorker(target)](#chromiumserviceworkertarget)
* [chromium.startTracing(page, [options])](#chromiumstarttracingpage-options)
* [chromium.stopTracing()](#chromiumstoptracing)
* [chromium.targets(context)](#chromiumtargetscontext)
* [chromium.waitForTarget(predicate[, options])](#chromiumwaitfortargetpredicate-options)
* [chromium.wsEndpoint()](#chromiumwsendpoint)
- [class: Dialog](#class-dialog)
* [dialog.accept([promptText])](#dialogacceptprompttext)
@ -297,6 +291,7 @@
- [class: Target](#class-target)
* [target.browser()](#targetbrowser)
* [target.browserContext()](#targetbrowsercontext)
* [target.createCDPSession()](#targetcreatecdpsession)
* [target.opener()](#targetopener)
* [target.page()](#targetpage)
* [target.type()](#targettype)
@ -353,7 +348,7 @@ const playwright = require('playwright');
`--load-extension=${pathToExtension}`
]
});
const targets = await browser.targets();
const targets = await browser.chromium.targets();
const backgroundPageTarget = targets.find(target => target.type() === 'background_page');
const backgroundPage = await backgroundPageTarget.page();
// Test the background page as you would any other page.
@ -601,28 +596,6 @@ Emitted when Playwright gets disconnected from the Chromium instance. This might
- Chromium is closed or crashed
- The [`browser.disconnect`](#browserdisconnect) method was called
#### event: 'targetchanged'
- <[Target]>
Emitted when the url of a target changes.
> **NOTE** This includes target changes in incognito browser contexts.
#### event: 'targetcreated'
- <[Target]>
Emitted when a target is created, for example when a new page is opened by [`window.open`](https://developer.mozilla.org/en-US/docs/Web/API/Window/open) or [`browser.newPage`](#browsernewpage).
> **NOTE** This includes target creations in incognito browser contexts.
#### event: 'targetdestroyed'
- <[Target]>
Emitted when a target is destroyed, for example when a page is closed.
> **NOTE** This includes target destructions in incognito browser contexts.
#### browser.browserContexts()
- returns: <[Array]<[BrowserContext]>>
@ -683,12 +656,6 @@ the method will return an array with all the pages in all browser contexts.
#### browser.process()
- returns: <?[ChildProcess]> Spawned browser process. Returns `null` if the browser instance was created with [`playwright.connect`](#playwrightconnectoptions) method.
#### browser.targets()
- returns: <[Array]<[Target]>>
An array of all active targets inside the Browser. In case of multiple browser contexts,
the method will return an array with all the targets in all browser contexts.
#### browser.userAgent()
- returns: <[Promise]<[string]>> Promise which resolves to the browser's original user agent.
@ -699,20 +666,6 @@ the method will return an array with all the targets in all browser contexts.
> **NOTE** the format of browser.version() might change with future releases of Chromium.
#### browser.waitForTarget(predicate[, options])
- `predicate` <[function]\([Target]\):[boolean]> A function to be run for every target
- `options` <[Object]>
- `timeout` <[number]> Maximum wait time in milliseconds. Pass `0` to disable the timeout. Defaults to 30 seconds.
- returns: <[Promise]<[Target]>> Promise which resolves to the first target found that matches the `predicate` function.
This searches for a target in all browser contexts.
An example of finding a target for a page opened via `window.open`:
```js
await page.evaluate(() => window.open('https://www.example.com/'));
const newWindowTarget = await browser.waitForTarget(target => target.url() === 'https://www.example.com/');
```
### class: BrowserContext
* extends: [EventEmitter](https://nodejs.org/api/events.html#events_class_eventemitter)
@ -737,21 +690,6 @@ await page.goto('https://example.com');
await context.close();
```
#### event: 'targetchanged'
- <[Target]>
Emitted when the url of a target inside the browser context changes.
#### event: 'targetcreated'
- <[Target]>
Emitted when a new target is created inside the browser context, for example when a new page is opened by [`window.open`](https://developer.mozilla.org/en-US/docs/Web/API/Window/open) or [`browserContext.newPage`](#browsercontextnewpage).
#### event: 'targetdestroyed'
- <[Target]>
Emitted when a target inside the browser context is destroyed, for example when a page is closed.
#### browserContext.browser()
- returns: <[Browser]>
@ -825,25 +763,6 @@ An array of all pages inside the browser context.
await browserContext.setCookies([cookieObject1, cookieObject2]);
```
#### browserContext.targets()
- returns: <[Array]<[Target]>>
An array of all active targets inside the browser context.
#### browserContext.waitForTarget(predicate[, options])
- `predicate` <[function]\([Target]\):[boolean]> A function to be run for every target
- `options` <[Object]>
- `timeout` <[number]> Maximum wait time in milliseconds. Pass `0` to disable the timeout. Defaults to 30 seconds.
- returns: <[Promise]<[Target]>> Promise which resolves to the first target found that matches the `predicate` function.
This searches for a target in this specific browser context.
An example of finding a target for a page opened via `window.open`:
```js
await page.evaluate(() => window.open('https://www.example.com/'));
const newWindowTarget = await browserContext.waitForTarget(target => target.url() === 'https://www.example.com/');
```
### class: Overrides
#### overrides.setGeolocation(options)
@ -1708,9 +1627,6 @@ await page.setViewport({
await page.goto('https://example.com');
```
#### page.target()
- returns: <[Target]> a target this page was created from.
#### page.title()
- returns: <[Promise]<[string]>> The page's title.
@ -2368,16 +2284,36 @@ await page.goto('https://www.google.com');
await page.chromium.stopTracing();
```
#### chromium.createBrowserCDPSession()
- returns: <[Promise]<[CDPSession]>>
#### event: 'targetchanged'
- <[Target]>
Creates a Chrome Devtools Protocol session attached to the browser.
Emitted when the url of a target changes.
#### chromium.createCDPSession(target)
- `target` <[Target]> Target to return CDP connection for.
- returns: <[Promise]<[CDPSession]>>
> **NOTE** This includes target changes in incognito browser contexts.
Creates a Chrome Devtools Protocol session attached to the target.
#### event: 'targetcreated'
- <[Target]>
Emitted when a target is created, for example when a new page is opened by [`window.open`](https://developer.mozilla.org/en-US/docs/Web/API/Window/open) or [`browser.newPage`](#browsernewpage).
> **NOTE** This includes target creations in incognito browser contexts.
#### event: 'targetdestroyed'
- <[Target]>
Emitted when a target is destroyed, for example when a page is closed.
> **NOTE** This includes target destructions in incognito browser contexts.
#### chromium.browserTarget()
- returns: <[Target]>
Returns browser target.
#### chromium.pageTarget(page)
- `page` <[Page]> Page to return target for.
- returns: <[Target]> a target given page was created from.
#### chromium.serviceWorker(target)
- `target` <[Target]> Target to treat as a service worker
@ -2398,6 +2334,27 @@ Only one trace can be active at a time per browser.
#### chromium.stopTracing()
- returns: <[Promise]<[Buffer]>> Promise which resolves to buffer with trace data.
#### chromium.targets(context)
- `context` <[BrowserContext]> Optional, if specified, only targets from this context are returned.
- returns: <[Array]<[Target]>>
An array of all active targets inside the Browser. In case of multiple browser contexts,
the method will return an array with all the targets in all browser contexts.
#### chromium.waitForTarget(predicate[, options])
- `predicate` <[function]\([Target]\):[boolean]> A function to be run for every target
- `options` <[Object]>
- `timeout` <[number]> Maximum wait time in milliseconds. Pass `0` to disable the timeout. Defaults to 30 seconds.
- returns: <[Promise]<[Target]>> Promise which resolves to the first target found that matches the `predicate` function.
This searches for a target in all browser contexts.
An example of finding a target for a page opened via `window.open`:
```js
await page.evaluate(() => window.open('https://www.example.com/'));
const newWindowTarget = await browser.chromium.waitForTarget(target => target.url() === 'https://www.example.com/');
```
#### chromium.wsEndpoint()
- returns: <[string]> Browser websocket url.
@ -3712,6 +3669,11 @@ Get the browser the target belongs to.
The browser context the target belongs to.
#### target.createCDPSession()
- returns: <[Promise]<[CDPSession]>>
Creates a Chrome Devtools Protocol session attached to the target.
#### target.opener()
- returns: <?[Target]>
@ -3743,7 +3705,7 @@ Useful links:
- Getting Started with DevTools Protocol: https://github.com/aslushnikov/getting-started-with-cdp/blob/master/README.md
```js
const client = await page.chromium.createCDPSession(target);
const client = await page.chromium.pageTarget(page).createCDPSession();
await client.send('Animation.enable');
client.on('Animation.animationCreated', () => console.log('Animation created!'));
const response = await client.send('Animation.getPlaybackRate');

View File

@ -32,7 +32,7 @@ export class Browser extends EventEmitter {
private _defaultViewport: Viewport;
private _process: childProcess.ChildProcess;
private _screenshotter = new Screenshotter();
private _connection: Connection;
_connection: Connection;
_client: CDPSession;
private _closeCallback: () => Promise<void>;
private _defaultContext: BrowserContext;
@ -66,7 +66,7 @@ export class Browser extends EventEmitter {
this._defaultViewport = defaultViewport;
this._process = process;
this._closeCallback = closeCallback || (() => Promise.resolve());
this.chromium = new Chromium(this._connection, this._client);
this.chromium = new Chromium(this);
this._defaultContext = new BrowserContext(this._client, this, null);
for (const contextId of contextIds)
@ -111,10 +111,8 @@ export class Browser extends EventEmitter {
assert(!this._targets.has(event.targetInfo.targetId), 'Target should not exist before targetCreated');
this._targets.set(event.targetInfo.targetId, target);
if (await target._initializedPromise) {
this.emit(Events.Browser.TargetCreated, target);
context.emit(Events.BrowserContext.TargetCreated, target);
}
if (await target._initializedPromise)
this.chromium.emit(Events.Chromium.TargetCreated, target);
}
async _targetDestroyed(event: { targetId: string; }) {
@ -122,10 +120,8 @@ export class Browser extends EventEmitter {
target._initializedCallback(false);
this._targets.delete(event.targetId);
target._closedCallback();
if (await target._initializedPromise) {
this.emit(Events.Browser.TargetDestroyed, target);
target.browserContext().emit(Events.BrowserContext.TargetDestroyed, target);
}
if (await target._initializedPromise)
this.chromium.emit(Events.Chromium.TargetDestroyed, target);
}
_targetInfoChanged(event: Protocol.Target.targetInfoChangedPayload) {
@ -134,10 +130,8 @@ export class Browser extends EventEmitter {
const previousURL = target.url();
const wasInitialized = target._isInitialized;
target._targetInfoChanged(event.targetInfo);
if (wasInitialized && previousURL !== target.url()) {
this.emit(Events.Browser.TargetChanged, target);
target.browserContext().emit(Events.BrowserContext.TargetChanged, target);
}
if (wasInitialized && previousURL !== target.url())
this.chromium.emit(Events.Chromium.TargetChanged, target);
}
async newPage(): Promise<Page> {
@ -156,28 +150,28 @@ export class Browser extends EventEmitter {
await this._client.send('Target.closeTarget', { targetId: target._targetId });
}
targets(): Target[] {
_allTargets(): Target[] {
return Array.from(this._targets.values()).filter(target => target._isInitialized);
}
async waitForTarget(predicate: (arg0: Target) => boolean, options: { timeout?: number; } | undefined = {}): Promise<Target> {
async _waitForTarget(predicate: (arg0: Target) => boolean, options: { timeout?: number; } | undefined = {}): Promise<Target> {
const {
timeout = 30000
} = options;
const existingTarget = this.targets().find(predicate);
const existingTarget = this._allTargets().find(predicate);
if (existingTarget)
return existingTarget;
let resolve;
const targetPromise = new Promise<Target>(x => resolve = x);
this.on(Events.Browser.TargetCreated, check);
this.on(Events.Browser.TargetChanged, check);
this.chromium.on(Events.Chromium.TargetCreated, check);
this.chromium.on(Events.Chromium.TargetChanged, check);
try {
if (!timeout)
return await targetPromise;
return await helper.waitWithTimeout(targetPromise, 'target', timeout);
} finally {
this.removeListener(Events.Browser.TargetCreated, check);
this.removeListener(Events.Browser.TargetChanged, check);
this.chromium.removeListener(Events.Chromium.TargetCreated, check);
this.chromium.removeListener(Events.Chromium.TargetChanged, check);
}
function check(target: Target) {

View File

@ -15,7 +15,6 @@
* limitations under the License.
*/
import { EventEmitter } from 'events';
import { assert } from '../helper';
import { filterCookies, NetworkCookie, rewriteCookies, SetNetworkCookieParam } from '../network';
import { Browser } from './Browser';
@ -24,30 +23,25 @@ import { Permissions } from './features/permissions';
import { Page } from './Page';
import { Target } from './Target';
export class BrowserContext extends EventEmitter {
export class BrowserContext {
readonly permissions: Permissions;
private _browser: Browser;
private _id: string;
constructor(client: CDPSession, browser: Browser, contextId: string | null) {
super();
this._browser = browser;
this._id = contextId;
this.permissions = new Permissions(client, contextId);
}
targets(): Target[] {
return this._browser.targets().filter(target => target.browserContext() === this);
}
waitForTarget(predicate: (arg0: Target) => boolean, options: { timeout?: number; } | undefined): Promise<Target> {
return this._browser.waitForTarget(target => target.browserContext() === this && predicate(target), options);
_targets(): Target[] {
return this._browser._allTargets().filter(target => target.browserContext() === this);
}
async pages(): Promise<Page[]> {
const pages = await Promise.all(
this.targets()
this._targets()
.filter(target => target.type() === 'page')
.map(target => target.page())
);

View File

@ -184,7 +184,7 @@ export class Launcher {
connection = new Connection('', transport, slowMo);
}
const browser = await Browser.create(connection, [], ignoreHTTPSErrors, defaultViewport, chromeProcess, gracefullyCloseChrome);
await browser.waitForTarget(t => t.type() === 'page');
await browser._waitForTarget(t => t.type() === 'page');
return browser;
} catch (e) {
killChrome();

View File

@ -59,7 +59,7 @@ export type Viewport = {
export class Page extends EventEmitter {
private _closed = false;
_client: CDPSession;
private _target: Target;
_target: Target;
private _keyboard: input.Keyboard;
private _mouse: input.Mouse;
private _timeoutSettings: TimeoutSettings;
@ -178,10 +178,6 @@ export class Page extends EventEmitter {
});
}
target(): Target {
return this._target;
}
browser(): Browser {
return this._target.browser();
}

View File

@ -85,7 +85,7 @@ export class Screenshotter {
}
private async _screenshot(page: Page, format: 'png' | 'jpeg', options: ScreenshotOptions): Promise<Buffer | string> {
await page._client.send('Target.activateTarget', {targetId: page.target()._targetId});
await page._client.send('Target.activateTarget', {targetId: page._target._targetId});
let clip = options.clip ? processClip(options.clip) : undefined;
const viewport = page.viewport();

View File

@ -28,7 +28,7 @@ export class Target {
private _targetInfo: Protocol.Target.TargetInfo;
private _browserContext: BrowserContext;
_targetId: string;
_sessionFactory: () => Promise<CDPSession>;
private _sessionFactory: () => Promise<CDPSession>;
private _ignoreHTTPSErrors: boolean;
private _defaultViewport: Viewport;
private _screenshotter: Screenshotter;
@ -118,6 +118,10 @@ export class Target {
return this.browser()._targets.get(openerId);
}
createCDPSession(): Promise<CDPSession> {
return this._sessionFactory();
}
_targetInfoChanged(targetInfo: Protocol.Target.TargetInfo) {
this._targetInfo = targetInfo;

View File

@ -1,26 +1,27 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.
export { ConsoleMessage } from '../console';
export { Dialog } from '../dialog';
export { ElementHandle } from '../dom';
export { TimeoutError } from '../Errors';
export { Frame } from '../frames';
export { Keyboard, Mouse } from '../input';
export { ExecutionContext, JSHandle } from '../javascript';
export { Request, Response } from '../network';
export { Browser } from './Browser';
export { BrowserContext } from './BrowserContext';
export { BrowserFetcher } from './BrowserFetcher';
export { Chromium } from './features/chromium';
export { CDPSession } from './Connection';
export { Dialog } from '../dialog';
export { ExecutionContext, JSHandle } from '../javascript';
export { ElementHandle } from '../dom';
export { Accessibility } from './features/accessibility';
export { Chromium } from './features/chromium';
export { Coverage } from './features/coverage';
export { Overrides } from './features/overrides';
export { Interception } from './features/interception';
export { Overrides } from './features/overrides';
export { PDF } from './features/pdf';
export { Permissions } from './features/permissions';
export { Worker, Workers } from './features/workers';
export { Frame } from '../frames';
export { Keyboard, Mouse } from '../input';
export { Request, Response } from '../network';
export { Page } from './Page';
export { Playwright } from './Playwright';
export { Target } from './Target';
export { ConsoleMessage } from '../console';

View File

@ -37,13 +37,10 @@ export const Events = {
},
Browser: {
TargetCreated: 'targetcreated',
TargetDestroyed: 'targetdestroyed',
TargetChanged: 'targetchanged',
Disconnected: 'disconnected'
},
BrowserContext: {
Chromium: {
TargetCreated: 'targetcreated',
TargetDestroyed: 'targetdestroyed',
TargetChanged: 'targetchanged',

View File

@ -14,19 +14,32 @@
* limitations under the License.
*/
const utils = require('./utils');
const {waitEvent} = utils;
const { waitEvent } = require('../../../test/utils');
module.exports.addTests = function({testRunner, expect, playwright, FFOX, CHROME, WEBKIT}) {
const {describe, xdescribe, fdescribe} = testRunner;
const {it, fit, xit} = testRunner;
const {beforeAll, beforeEach, afterAll, afterEach} = testRunner;
describe('Chromium', function() {
it('should work across sessions', async function({browser, server}) {
expect(browser.browserContexts().length).toBe(2);
const context = await browser.createIncognitoBrowserContext();
expect(browser.browserContexts().length).toBe(3);
const remoteBrowser = await playwright.connect({
browserWSEndpoint: browser.chromium.wsEndpoint()
});
const contexts = remoteBrowser.browserContexts();
expect(contexts.length).toBe(3);
remoteBrowser.disconnect();
await context.close();
});
});
describe('Target', function() {
// FIXME(WebKit): need to enable agents in the inspected page to listen for updates.
it.skip(WEBKIT)('Browser.targets should return all of the targets', async({page, server, browser}) => {
it('Chromium.targets should return all of the targets', async({page, server, browser}) => {
// The pages will be the testing page and the original newtab page
const targets = browser.targets();
const targets = browser.chromium.targets();
expect(targets.some(target => target.type() === 'page' &&
target.url() === 'about:blank')).toBeTruthy('Missing blank page');
expect(targets.some(target => target.type() === 'browser')).toBeTruthy('Missing browser target');
@ -38,8 +51,8 @@ module.exports.addTests = function({testRunner, expect, playwright, FFOX, CHROME
expect(allPages).toContain(page);
expect(allPages[0]).not.toBe(allPages[1]);
});
it.skip(WEBKIT)('should contain browser target', async({browser}) => {
const targets = browser.targets();
it('should contain browser target', async({browser}) => {
const targets = browser.chromium.targets();
const browserTarget = targets.find(target => target.type() === 'browser');
expect(browserTarget).toBeTruthy();
});
@ -50,10 +63,9 @@ module.exports.addTests = function({testRunner, expect, playwright, FFOX, CHROME
expect(await originalPage.evaluate(() => ['Hello', 'world'].join(' '))).toBe('Hello world');
expect(await originalPage.$('body')).toBeTruthy();
});
// FIXME(WebKit): need to enable agents in the inspected page to listen for updates.
it.skip(WEBKIT)('should report when a new page is created and closed', async({page, server, context}) => {
it('should report when a new page is created and closed', async({browser, page, server, context}) => {
const [otherPage] = await Promise.all([
context.waitForTarget(target => target.url() === server.CROSS_PROCESS_PREFIX + '/empty.html').then(target => target.page()),
browser.chromium.waitForTarget(target => target.url() === server.CROSS_PROCESS_PREFIX + '/empty.html').then(target => target.page()),
page.evaluate(url => window.open(url), server.CROSS_PROCESS_PREFIX + '/empty.html'),
]);
expect(otherPage.url()).toContain(server.CROSS_PROCESS_PREFIX);
@ -64,73 +76,73 @@ module.exports.addTests = function({testRunner, expect, playwright, FFOX, CHROME
expect(allPages).toContain(page);
expect(allPages).toContain(otherPage);
const closePagePromise = new Promise(fulfill => context.once('targetdestroyed', target => fulfill(target.page())));
const closePagePromise = new Promise(fulfill => browser.chromium.once('targetdestroyed', target => fulfill(target.page())));
await otherPage.close();
expect(await closePagePromise).toBe(otherPage);
allPages = await Promise.all(context.targets().map(target => target.page()));
allPages = await Promise.all(browser.chromium.targets().map(target => target.page()));
expect(allPages).toContain(page);
expect(allPages).not.toContain(otherPage);
});
it.skip(FFOX || WEBKIT)('should report when a service worker is created and destroyed', async({page, server, context}) => {
it('should report when a service worker is created and destroyed', async({browser, page, server, context}) => {
await page.goto(server.EMPTY_PAGE);
const createdTarget = new Promise(fulfill => context.once('targetcreated', target => fulfill(target)));
const createdTarget = new Promise(fulfill => browser.chromium.once('targetcreated', target => fulfill(target)));
await page.goto(server.PREFIX + '/serviceworkers/empty/sw.html');
expect((await createdTarget).type()).toBe('service_worker');
expect((await createdTarget).url()).toBe(server.PREFIX + '/serviceworkers/empty/sw.js');
const destroyedTarget = new Promise(fulfill => context.once('targetdestroyed', target => fulfill(target)));
const destroyedTarget = new Promise(fulfill => browser.chromium.once('targetdestroyed', target => fulfill(target)));
await page.evaluate(() => window.registrationPromise.then(registration => registration.unregister()));
expect(await destroyedTarget).toBe(await createdTarget);
});
it.skip(FFOX || WEBKIT)('should create a worker from a service worker', async({browser, page, server, context}) => {
it('should create a worker from a service worker', async({browser, page, server, context}) => {
await page.goto(server.PREFIX + '/serviceworkers/empty/sw.html');
const target = await context.waitForTarget(target => target.type() === 'service_worker');
const target = await browser.chromium.waitForTarget(target => target.type() === 'service_worker');
const worker = await browser.chromium.serviceWorker(target);
expect(await worker.evaluate(() => self.toString())).toBe('[object ServiceWorkerGlobalScope]');
});
it.skip(FFOX || WEBKIT)('should create a worker from a shared worker', async({browser, page, server, context}) => {
it('should create a worker from a shared worker', async({browser, page, server, context}) => {
await page.goto(server.EMPTY_PAGE);
await page.evaluate(() => {
new SharedWorker('data:text/javascript,console.log("hi")');
});
const target = await context.waitForTarget(target => target.type() === 'shared_worker');
const target = await browser.chromium.waitForTarget(target => target.type() === 'shared_worker');
const worker = await browser.chromium.serviceWorker(target);
expect(await worker.evaluate(() => self.toString())).toBe('[object SharedWorkerGlobalScope]');
});
it.skip(WEBKIT)('should report when a target url changes', async({page, server, context}) => {
it('should report when a target url changes', async({browser, page, server, context}) => {
await page.goto(server.EMPTY_PAGE);
let changedTarget = new Promise(fulfill => context.once('targetchanged', target => fulfill(target)));
let changedTarget = new Promise(fulfill => browser.chromium.once('targetchanged', target => fulfill(target)));
await page.goto(server.CROSS_PROCESS_PREFIX + '/');
expect((await changedTarget).url()).toBe(server.CROSS_PROCESS_PREFIX + '/');
changedTarget = new Promise(fulfill => context.once('targetchanged', target => fulfill(target)));
changedTarget = new Promise(fulfill => browser.chromium.once('targetchanged', target => fulfill(target)));
await page.goto(server.EMPTY_PAGE);
expect((await changedTarget).url()).toBe(server.EMPTY_PAGE);
});
it.skip(FFOX || WEBKIT)('should not report uninitialized pages', async({page, server, context}) => {
it('should not report uninitialized pages', async({browser, page, server, context}) => {
let targetChanged = false;
const listener = () => targetChanged = true;
context.on('targetchanged', listener);
const targetPromise = new Promise(fulfill => context.once('targetcreated', target => fulfill(target)));
browser.chromium.on('targetchanged', listener);
const targetPromise = new Promise(fulfill => browser.chromium.once('targetcreated', target => fulfill(target)));
const newPagePromise = context.newPage();
const target = await targetPromise;
expect(target.url()).toBe('about:blank');
const newPage = await newPagePromise;
const targetPromise2 = new Promise(fulfill => context.once('targetcreated', target => fulfill(target)));
const targetPromise2 = new Promise(fulfill => browser.chromium.once('targetcreated', target => fulfill(target)));
const evaluatePromise = newPage.evaluate(() => window.open('about:blank'));
const target2 = await targetPromise2;
expect(target2.url()).toBe('about:blank');
await evaluatePromise;
await newPage.close();
expect(targetChanged).toBe(false, 'target should not be reported as changed');
context.removeListener('targetchanged', listener);
browser.chromium.removeListener('targetchanged', listener);
});
it.skip(WEBKIT)('should not crash while redirecting if original request was missed', async({page, server, context}) => {
it('should not crash while redirecting if original request was missed', async({browser, page, server, context}) => {
let serverResponse = null;
server.setRoute('/one-style.css', (req, res) => serverResponse = res);
// Open a new page. Use window.open to connect to the page later.
@ -139,7 +151,7 @@ module.exports.addTests = function({testRunner, expect, playwright, FFOX, CHROME
server.waitForRequest('/one-style.css')
]);
// Connect to the opened page.
const target = await context.waitForTarget(target => target.url().includes('one-style.html'));
const target = await browser.chromium.waitForTarget(target => target.url().includes('one-style.html'));
const newPage = await target.page();
// Issue a redirect.
serverResponse.writeHead(302, { location: '/injectedstyle.css' });
@ -149,22 +161,40 @@ module.exports.addTests = function({testRunner, expect, playwright, FFOX, CHROME
// Cleanup.
await newPage.close();
});
it.skip(WEBKIT)('should have an opener', async({page, server, context}) => {
it('should have an opener', async({browser, page, server, context}) => {
await page.goto(server.EMPTY_PAGE);
const [createdTarget] = await Promise.all([
new Promise(fulfill => context.once('targetcreated', target => fulfill(target))),
new Promise(fulfill => browser.chromium.once('targetcreated', target => fulfill(target))),
page.goto(server.PREFIX + '/popup/window-open.html')
]);
expect((await createdTarget.page()).url()).toBe(server.PREFIX + '/popup/popup.html');
expect(createdTarget.opener()).toBe(page.target());
expect(page.target().opener()).toBe(null);
expect(createdTarget.opener()).toBe(browser.chromium.pageTarget(page));
expect(browser.chromium.pageTarget(page).opener()).toBe(null);
});
});
describe('Browser.waitForTarget', () => {
describe('Chromium.waitForTarget', () => {
it('should wait for a target', async function({browser, server}) {
const context = await browser.createIncognitoBrowserContext();
let resolved = false;
const targetPromise = browser.chromium.waitForTarget(target => target.browserContext() === context && target.url() === server.EMPTY_PAGE);
targetPromise.then(() => resolved = true);
const page = await context.newPage();
expect(resolved).toBe(false);
await page.goto(server.EMPTY_PAGE);
const target = await targetPromise;
expect(await target.page()).toBe(page);
await context.close();
});
it('should timeout waiting for a non-existent target', async function({browser, server}) {
const context = await browser.createIncognitoBrowserContext();
const error = await browser.chromium.waitForTarget(target => target.browserContext() === context && target.url() === server.EMPTY_PAGE, {timeout: 1}).catch(e => e);
expect(error).toBeInstanceOf(playwright.errors.TimeoutError);
await context.close();
});
it('should wait for a target', async function({browser, server}) {
let resolved = false;
const targetPromise = browser.waitForTarget(target => target.url() === server.EMPTY_PAGE);
const targetPromise = browser.chromium.waitForTarget(target => target.url() === server.EMPTY_PAGE);
targetPromise.then(() => resolved = true);
const page = await browser.newPage();
expect(resolved).toBe(false);
@ -173,9 +203,9 @@ module.exports.addTests = function({testRunner, expect, playwright, FFOX, CHROME
expect(await target.page()).toBe(page);
await page.close();
});
it.skip(WEBKIT)('should timeout waiting for a non-existent target', async function({browser, server}) {
it('should timeout waiting for a non-existent target', async function({browser, server}) {
let error = null;
await browser.waitForTarget(target => target.url() === server.EMPTY_PAGE, {
await browser.chromium.waitForTarget(target => target.url() === server.EMPTY_PAGE, {
timeout: 1
}).catch(e => error = e);
expect(error).toBeInstanceOf(playwright.errors.TimeoutError);

View File

@ -14,31 +14,33 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { EventEmitter } from 'events';
import { assert } from '../../helper';
import { Browser } from '../Browser';
import { BrowserContext } from '../BrowserContext';
import { CDPSession, Connection } from '../Connection';
import { Page } from '../Page';
import { readProtocolStream } from '../protocolHelper';
import { Target } from '../Target';
import { Worker } from './workers';
export class Chromium {
export class Chromium extends EventEmitter {
private _connection: Connection;
private _client: CDPSession;
private _recording = false;
private _path = '';
private _tracingClient: CDPSession | undefined;
private _browser: Browser;
constructor(connection: Connection, client: CDPSession) {
this._connection = connection;
this._client = client;
constructor(browser: Browser) {
super();
this._connection = browser._connection;
this._client = browser._client;
this._browser = browser;
}
createBrowserCDPSession(): Promise<CDPSession> {
return this._connection.createBrowserSession();
}
createCDPSession(target: Target): Promise<CDPSession> {
return target._sessionFactory();
browserTarget(): Target {
return [...this._browser._targets.values()].find(t => t.type() === 'browser');
}
serviceWorker(target: Target): Promise<Worker | null> {
@ -84,6 +86,19 @@ export class Chromium {
return contentPromise;
}
targets(context?: BrowserContext): Target[] {
const targets = this._browser._allTargets();
return context ? targets.filter(t => t.browserContext() === context) : targets;
}
pageTarget(page: Page): Target {
return page._target;
}
waitForTarget(predicate: (arg0: Target) => boolean, options: { timeout?: number; } | undefined = {}): Promise<Target> {
return this._browser._waitForTarget(predicate, options);
}
wsEndpoint(): string {
return this._connection.url();
}

View File

@ -15,9 +15,6 @@
* limitations under the License.
*/
const utils = require('../../../test/utils');
const {waitEvent} = utils;
module.exports.addTests = function ({ testRunner, expect }) {
const {describe, xdescribe, fdescribe} = testRunner;
const {it, fit, xit} = testRunner;

View File

@ -105,23 +105,21 @@ export class Browser extends EventEmitter {
return this._process;
}
async waitForTarget(predicate: (target: Target) => boolean, options: { timeout?: number; } = {}): Promise<Target> {
async _waitForTarget(predicate: (target: Target) => boolean, options: { timeout?: number; } = {}): Promise<Target> {
const {
timeout = 30000
} = options;
const existingTarget = this.targets().find(predicate);
const existingTarget = this._allTargets().find(predicate);
if (existingTarget)
return existingTarget;
let resolve;
const targetPromise = new Promise<Target>(x => resolve = x);
this.on(Events.Browser.TargetCreated, check);
this.on('targetchanged', check);
try {
if (!timeout)
return await targetPromise;
return await helper.waitWithTimeout(targetPromise, 'target', timeout);
} finally {
this.removeListener(Events.Browser.TargetCreated, check);
this.removeListener('targetchanged', check);
}
@ -148,7 +146,7 @@ export class Browser extends EventEmitter {
return await Promise.all(pageTargets.map(target => target.page()));
}
targets() {
_allTargets() {
return Array.from(this._targets.values());
}
@ -163,23 +161,17 @@ export class Browser extends EventEmitter {
openerPage.emit(Events.Page.Popup, popupPage);
}
}
this.emit(Events.Browser.TargetCreated, target);
context.emit(Events.BrowserContext.TargetCreated, target);
}
_onTargetDestroyed({targetId}) {
const target = this._targets.get(targetId);
this._targets.delete(targetId);
target._closedCallback();
this.emit(Events.Browser.TargetDestroyed, target);
target.browserContext().emit(Events.BrowserContext.TargetDestroyed, target);
}
_onTargetInfoChanged({targetId, url}) {
const target = this._targets.get(targetId);
target._url = url;
this.emit(Events.Browser.TargetChanged, target);
target.browserContext().emit(Events.BrowserContext.TargetChanged, target);
}
async close() {
@ -243,40 +235,32 @@ export class Target {
}
}
export class BrowserContext extends EventEmitter {
export class BrowserContext {
_connection: Connection;
_browser: Browser;
_browserContextId: string;
readonly permissions: Permissions;
constructor(connection: Connection, browser: Browser, browserContextId: string | null) {
super();
this._connection = connection;
this._browser = browser;
this._browserContextId = browserContextId;
this.permissions = new Permissions(connection, browserContextId);
}
targets(): Array<Target> {
return this._browser.targets().filter(target => target.browserContext() === this);
_targets(): Array<Target> {
return this._browser._allTargets().filter(target => target.browserContext() === this);
}
async pages(): Promise<Array<Page>> {
const pages = await Promise.all(
this.targets()
this._targets()
.filter(target => target.type() === 'page')
.map(target => target.page())
);
return pages.filter(page => !!page);
}
waitForTarget(predicate: (arg0: Target) => boolean, options: { timeout?: number; } | undefined): Promise<Target> {
return this._browser.waitForTarget(target => target.browserContext() === this && predicate(target), options);
}
isIncognito(): boolean {
return !!this._browserContextId;
}

View File

@ -165,7 +165,7 @@ export class Launcher {
const browser = await Browser.create(connection, defaultViewport, firefoxProcess, gracefullyCloseFirefox);
if (ignoreHTTPSErrors)
await connection.send('Browser.setIgnoreHTTPSErrors', {enabled: true});
await browser.waitForTarget(t => t.type() === 'page');
await browser._waitForTarget(t => t.type() === 'page');
return browser;
} catch (e) {
killFirefox();

View File

@ -291,10 +291,6 @@ export class Page extends EventEmitter {
return this._target.browser();
}
target() {
return this._target;
}
url() {
return this._frameManager.mainFrame().url();
}

View File

@ -3,7 +3,7 @@
export { TimeoutError } from '../Errors';
export { Keyboard, Mouse } from '../input';
export { Browser, BrowserContext, Target } from './Browser';
export { Browser, BrowserContext } from './Browser';
export { BrowserFetcher } from './BrowserFetcher';
export { Dialog } from '../dialog';
export { ExecutionContext, JSHandle } from '../javascript';

View File

@ -37,15 +37,6 @@ export const Events = {
},
Browser: {
TargetCreated: 'targetcreated',
TargetDestroyed: 'targetdestroyed',
TargetChanged: 'targetchanged',
Disconnected: 'disconnected'
},
BrowserContext: {
TargetCreated: 'targetcreated',
TargetDestroyed: 'targetdestroyed',
TargetChanged: 'targetchanged',
}
};

View File

@ -36,17 +36,7 @@ export class Browser extends EventEmitter {
private _contexts = new Map<string, BrowserContext>();
_targets = new Map<string, Target>();
private _eventListeners: RegisteredListener[];
_waitForFirstTarget: Promise<void>;
private _waitForFirstTargetCallback: () => void;
static async create(
connection: Connection,
defaultViewport: Viewport | null,
process: childProcess.ChildProcess | null,
closeCallback?: (() => Promise<void>)) {
const browser = new Browser(connection, defaultViewport, process, closeCallback);
return browser;
}
private _privateEvents = new EventEmitter();
constructor(
connection: Connection,
@ -74,7 +64,6 @@ export class Browser extends EventEmitter {
// Taking multiple screenshots in parallel doesn't work well, so we serialize them.
this._screenshotTaskQueue = new TaskQueue();
this._waitForFirstTarget = new Promise(f => this._waitForFirstTargetCallback = f);
}
async userAgent(): Promise<string> {
@ -128,7 +117,7 @@ export class Browser extends EventEmitter {
return Array.from(this._targets.values());
}
async waitForTarget(predicate: (arg0: Target) => boolean, options: { timeout?: number; } | undefined = {}): Promise<Target> {
async _waitForTarget(predicate: (arg0: Target) => boolean, options: { timeout?: number; } | undefined = {}): Promise<Target> {
const {
timeout = 30000
} = options;
@ -137,15 +126,13 @@ export class Browser extends EventEmitter {
return existingTarget;
let resolve;
const targetPromise = new Promise<Target>(x => resolve = x);
this.on(Events.Browser.TargetCreated, check);
this.on(Events.Browser.TargetChanged, check);
this._privateEvents.on(BrowserEvents.TargetCreated, check);
try {
if (!timeout)
return await targetPromise;
return await helper.waitWithTimeout(targetPromise, 'target', timeout);
} finally {
this.removeListener(Events.Browser.TargetCreated, check);
this.removeListener(Events.Browser.TargetChanged, check);
this._privateEvents.removeListener(BrowserEvents.TargetCreated, check);
}
function check(target: Target) {
@ -175,17 +162,13 @@ export class Browser extends EventEmitter {
context = this._defaultContext;
const target = new Target(targetInfo, context);
this._targets.set(targetInfo.targetId, target);
this.emit(Events.Browser.TargetCreated, target);
context.emit(Events.BrowserContext.TargetCreated, target);
this._waitForFirstTargetCallback();
this._privateEvents.emit(BrowserEvents.TargetCreated, target);
}
_onTargetDestroyed({targetId}) {
const target = this._targets.get(targetId);
this._targets.delete(targetId);
target._closedCallback();
this.emit(Events.Browser.TargetDestroyed, target);
target.browserContext().emit(Events.BrowserContext.TargetDestroyed, target);
}
async _onProvisionalTargetCommitted({oldTargetId, newTargetId}) {
@ -199,11 +182,6 @@ export class Browser extends EventEmitter {
newTarget._pagePromise = oldTarget._pagePromise;
}
_onTargetChanged(target: Target) {
this.emit(Events.BrowserContext.TargetChanged, target);
target.browserContext().emit(Events.BrowserContext.TargetChanged, target);
}
disconnect() {
throw new Error('Unsupported operation');
}
@ -218,28 +196,22 @@ export class Browser extends EventEmitter {
}
}
export class BrowserContext extends EventEmitter {
export class BrowserContext {
private _browser: Browser;
_id: string;
constructor(browser: Browser, contextId?: string) {
super();
this._browser = browser;
this._id = contextId;
}
targets(): Target[] {
_targets(): Target[] {
return this._browser.targets().filter(target => target.browserContext() === this);
}
waitForTarget(predicate: (arg0: Target) => boolean, options: { timeout?: number; } | undefined): Promise<Target> {
return this._browser.waitForTarget(target => target.browserContext() === this && predicate(target), options);
}
async pages(): Promise<Page[]> {
await this._browser._waitForFirstTarget;
const pages = await Promise.all(
this.targets()
this._targets()
.filter(target => target.type() === 'page')
.map(target => target.page())
);
@ -281,3 +253,8 @@ export class BrowserContext extends EventEmitter {
await this._browser._connection.send('Browser.deleteAllCookies', { browserContextId: this._id });
}
}
const BrowserEvents = {
TargetCreated: Symbol('BrowserEvents.TargetCreated'),
TargetDestroyed: Symbol('BrowserEvents.TargetDestroyed'),
};

View File

@ -127,6 +127,7 @@ export class Launcher {
const transport = new PipeTransport(webkitProcess.stdio[3] as NodeJS.WritableStream, webkitProcess.stdio[4] as NodeJS.ReadableStream);
connection = new Connection('', transport, slowMo);
const browser = new Browser(connection, defaultViewport, webkitProcess, gracefullyCloseWebkit);
await browser._waitForTarget(t => t.type() === 'page');
return browser;
} catch (e) {
killWebKit();

View File

@ -144,10 +144,6 @@ export class Page extends EventEmitter {
await this._initialize().catch(e => debugError('failed to enable agents after swap: ' + e));
}
target(): Target {
return this._target;
}
browser(): Browser {
return this._target.browser();
}
@ -255,11 +251,11 @@ export class Page extends EventEmitter {
return await this._frameManager.mainFrame().content();
}
async setContent(html: string, options: { timeout?: number; waitUntil?: string | string[]; } | undefined) {
async setContent(html: string, options: { timeout?: number; waitUntil?: string | string[]; } = {}) {
await this._frameManager.mainFrame().setContent(html, options);
}
async goto(url: string, options: { referer?: string; timeout?: number; waitUntil?: string | string[]; } | undefined): Promise<network.Response | null> {
async goto(url: string, options: { referer?: string; timeout?: number; waitUntil?: string | string[]; } = {}): Promise<network.Response | null> {
return await this._frameManager.mainFrame().goto(url, options);
}
@ -494,11 +490,11 @@ export class Page extends EventEmitter {
return this.mainFrame().select(selector, ...values);
}
type(selector: string | types.Selector, text: string, options: { delay: (number | undefined); } | undefined) {
type(selector: string | types.Selector, text: string, options?: { delay: (number | undefined); }) {
return this.mainFrame().type(selector, text, options);
}
waitFor(selectorOrFunctionOrTimeout: (string | number | Function), options: { visible?: boolean; hidden?: boolean; timeout?: number; polling?: string | number; } = {}, ...args: any[]): Promise<js.JSHandle> {
waitFor(selectorOrFunctionOrTimeout: (string | number | Function), options?: { visible?: boolean; hidden?: boolean; timeout?: number; polling?: string | number; }, ...args: any[]): Promise<js.JSHandle> {
return this.mainFrame().waitFor(selectorOrFunctionOrTimeout, options, ...args);
}
@ -510,7 +506,7 @@ export class Page extends EventEmitter {
return this.mainFrame().waitForXPath(xpath, options);
}
waitForFunction(pageFunction: Function | string, options: types.WaitForFunctionOptions, ...args: any[]): Promise<js.JSHandle> {
waitForFunction(pageFunction: Function | string, options?: types.WaitForFunctionOptions, ...args: any[]): Promise<js.JSHandle> {
return this.mainFrame().waitForFunction(pageFunction, options, ...args);
}
}

View File

@ -42,25 +42,6 @@ export class Target {
this._pagePromise = null;
this._url = url;
this._isClosedPromise = new Promise(fulfill => this._closedCallback = fulfill);
if (type === 'page') {
const session = this._browserContext.browser()._connection.session(this._targetId);
this._eventListeners = [
// FIXME: we could use Page.frameStartedLoading if it had url info.
helper.addEventListener(session, 'Page.frameNavigated', this._onFrameNavigated.bind(this)),
];
}
}
async _onFrameNavigated(params: Protocol.Page.frameNavigatedPayload) {
// Check if main frame, get url from the event.
// Skip child frames.
if (params.frame.parentId)
return;
const url = params.frame.url;
if (this._url !== url) {
this._url = url;
this.browser()._onTargetChanged(this);
}
}
async page(): Promise<Page | null> {

View File

@ -11,6 +11,5 @@ export { Mouse, Keyboard } from '../input';
export { Request, Response } from '../network';
export { Page } from './Page';
export { Playwright } from './Playwright';
export { Target } from './Target';
export { Dialog } from '../dialog';
export { ConsoleMessage } from '../console';

View File

@ -33,15 +33,6 @@ export const Events = {
},
Browser: {
TargetCreated: 'targetcreated',
TargetDestroyed: 'targetdestroyed',
TargetChanged: 'targetchanged',
Disconnected: 'disconnected'
},
BrowserContext: {
TargetCreated: 'targetcreated',
TargetDestroyed: 'targetdestroyed',
TargetChanged: 'targetchanged',
},
};

View File

@ -23,7 +23,7 @@ module.exports.addTests = function({testRunner, expect, FFOX, CHROME, WEBKIT}) {
describe('Chromium.createCDPSession', function() {
it('should work', async function({page, browser, server}) {
const client = await browser.chromium.createCDPSession(page.target());
const client = await browser.chromium.pageTarget(page).createCDPSession();
await Promise.all([
client.send('Runtime.enable'),
@ -33,7 +33,7 @@ module.exports.addTests = function({testRunner, expect, FFOX, CHROME, WEBKIT}) {
expect(foo).toBe('bar');
});
it('should send events', async function({page, browser, server}) {
const client = await browser.chromium.createCDPSession(page.target());
const client = await browser.chromium.pageTarget(page).createCDPSession();
await client.send('Network.enable');
const events = [];
client.on('Network.requestWillBeSent', event => events.push(event));
@ -41,7 +41,7 @@ module.exports.addTests = function({testRunner, expect, FFOX, CHROME, WEBKIT}) {
expect(events.length).toBe(1);
});
it('should enable and disable domains independently', async function({page, browser, server}) {
const client = await browser.chromium.createCDPSession(page.target());
const client = await browser.chromium.pageTarget(page).createCDPSession();
await client.send('Runtime.enable');
await client.send('Debugger.enable');
// JS coverage enables and then disables Debugger domain.
@ -56,7 +56,7 @@ module.exports.addTests = function({testRunner, expect, FFOX, CHROME, WEBKIT}) {
expect(event.url).toBe('foo.js');
});
it('should be able to detach session', async function({page, browser, server}) {
const client = await browser.chromium.createCDPSession(page.target());
const client = await browser.chromium.pageTarget(page).createCDPSession();
await client.send('Runtime.enable');
const evalResponse = await client.send('Runtime.evaluate', {expression: '1 + 2', returnByValue: true});
expect(evalResponse.result.value).toBe(3);
@ -70,7 +70,7 @@ module.exports.addTests = function({testRunner, expect, FFOX, CHROME, WEBKIT}) {
expect(error.message).toContain('Session closed.');
});
it('should throw nice errors', async function({page, browser}) {
const client = await browser.chromium.createCDPSession(page.target());
const client = await browser.chromium.pageTarget(page).createCDPSession();
const error = await theSourceOfTheProblems().catch(error => error);
expect(error.stack).toContain('theSourceOfTheProblems');
expect(error.message).toContain('ThisCommand.DoesNotExist');

View File

@ -56,7 +56,7 @@ module.exports.addTests = function({testRunner, expect, playwright, FFOX, CHROME
const page = await context.newPage();
await page.goto(server.EMPTY_PAGE);
const [popupTarget] = await Promise.all([
utils.waitEvent(browser, 'targetcreated'),
utils.waitEvent(page, 'popup'),
page.evaluate(url => window.open(url), server.EMPTY_PAGE)
]);
expect(popupTarget.browserContext()).toBe(context);
@ -65,9 +65,9 @@ module.exports.addTests = function({testRunner, expect, playwright, FFOX, CHROME
it.skip(WEBKIT)('should fire target events', async function({browser, server}) {
const context = await browser.createIncognitoBrowserContext();
const events = [];
context.on('targetcreated', target => events.push('CREATED: ' + target.url()));
context.on('targetchanged', target => events.push('CHANGED: ' + target.url()));
context.on('targetdestroyed', target => events.push('DESTROYED: ' + target.url()));
browser.chromium.on('targetcreated', target => events.push('CREATED: ' + target.url()));
browser.chromium.on('targetchanged', target => events.push('CHANGED: ' + target.url()));
browser.chromium.on('targetdestroyed', target => events.push('DESTROYED: ' + target.url()));
const page = await context.newPage();
await page.goto(server.EMPTY_PAGE);
await page.close();
@ -78,30 +78,12 @@ module.exports.addTests = function({testRunner, expect, playwright, FFOX, CHROME
]);
await context.close();
});
it('should wait for a target', async function({browser, server}) {
const context = await browser.createIncognitoBrowserContext();
let resolved = false;
const targetPromise = context.waitForTarget(target => target.url() === server.EMPTY_PAGE);
targetPromise.then(() => resolved = true);
const page = await context.newPage();
expect(resolved).toBe(false);
await page.goto(server.EMPTY_PAGE);
const target = await targetPromise;
expect(await target.page()).toBe(page);
await context.close();
});
it('should timeout waiting for a non-existent target', async function({browser, server}) {
const context = await browser.createIncognitoBrowserContext();
const error = await context.waitForTarget(target => target.url() === server.EMPTY_PAGE, {timeout: 1}).catch(e => e);
expect(error).toBeInstanceOf(playwright.errors.TimeoutError);
await context.close();
});
it('should isolate localStorage and cookies', async function({browser, server}) {
// Create two incognito contexts.
const context1 = await browser.createIncognitoBrowserContext();
const context2 = await browser.createIncognitoBrowserContext();
expect(context1.targets().length).toBe(0);
expect(context2.targets().length).toBe(0);
expect(browser.chromium.targets(context1).length).toBe(0);
expect(browser.chromium.targets(context2).length).toBe(0);
// Create a page in first incognito context.
const page1 = await context1.newPage();
@ -111,8 +93,8 @@ module.exports.addTests = function({testRunner, expect, playwright, FFOX, CHROME
document.cookie = 'name=page1';
});
expect(context1.targets().length).toBe(1);
expect(context2.targets().length).toBe(0);
expect(browser.chromium.targets(context1).length).toBe(1);
expect(browser.chromium.targets(context2).length).toBe(0);
// Create a page in second incognito context.
const page2 = await context2.newPage();
@ -122,10 +104,10 @@ module.exports.addTests = function({testRunner, expect, playwright, FFOX, CHROME
document.cookie = 'name=page2';
});
expect(context1.targets().length).toBe(1);
expect(context1.targets()[0]).toBe(page1.target());
expect(context2.targets().length).toBe(1);
expect(context2.targets()[0]).toBe(page2.target());
expect(browser.chromium.targets(context1).length).toBe(1);
expect(browser.chromium.targets(context1)[0]).toBe(browser.chromium.pageTarget(page1));
expect(browser.chromium.targets(context2).length).toBe(1);
expect(browser.chromium.targets(context2)[0]).toBe(browser.chromium.pageTarget(page2));
// Make sure pages don't share localstorage or cookies.
expect(await page1.evaluate(() => localStorage.getItem('name'))).toBe('page1');
@ -140,17 +122,5 @@ module.exports.addTests = function({testRunner, expect, playwright, FFOX, CHROME
]);
expect(browser.browserContexts().length).toBe(1);
});
it.skip(WEBKIT)('should work across sessions', async function({browser, server}) {
expect(browser.browserContexts().length).toBe(1);
const context = await browser.createIncognitoBrowserContext();
expect(browser.browserContexts().length).toBe(2);
const remoteBrowser = await playwright.connect({
browserWSEndpoint: browser.chromium.wsEndpoint()
});
const contexts = remoteBrowser.browserContexts();
expect(contexts.length).toBe(2);
remoteBrowser.disconnect();
await context.close();
});
});
};

View File

@ -14,6 +14,8 @@
* limitations under the License.
*/
const utils = require('./utils');
module.exports.addLauncherTests = function({testRunner, expect, defaultBrowserOptions, playwright}) {
const {describe, xdescribe, fdescribe} = testRunner;
const {it, fit, xit} = testRunner;
@ -94,6 +96,57 @@ module.exports.addLauncherTests = function({testRunner, expect, defaultBrowserOp
});
});
});
describe('Browser target events', function() {
it('should work', async({server}) => {
const browser = await playwright.launch(defaultBrowserOptions);
const events = [];
browser.chromium.on('targetcreated', () => events.push('CREATED'));
browser.chromium.on('targetchanged', () => events.push('CHANGED'));
browser.chromium.on('targetdestroyed', () => events.push('DESTROYED'));
const page = await browser.newPage();
await page.goto(server.EMPTY_PAGE);
await page.close();
expect(events).toEqual(['CREATED', 'CHANGED', 'DESTROYED']);
await browser.close();
});
});
describe('Browser.Events.disconnected', function() {
it('should be emitted when: browser gets closed, disconnected or underlying websocket gets closed', async() => {
const originalBrowser = await playwright.launch(defaultBrowserOptions);
const browserWSEndpoint = originalBrowser.chromium.wsEndpoint();
const remoteBrowser1 = await playwright.connect({browserWSEndpoint});
const remoteBrowser2 = await playwright.connect({browserWSEndpoint});
let disconnectedOriginal = 0;
let disconnectedRemote1 = 0;
let disconnectedRemote2 = 0;
originalBrowser.on('disconnected', () => ++disconnectedOriginal);
remoteBrowser1.on('disconnected', () => ++disconnectedRemote1);
remoteBrowser2.on('disconnected', () => ++disconnectedRemote2);
await Promise.all([
utils.waitEvent(remoteBrowser2, 'disconnected'),
remoteBrowser2.disconnect(),
]);
expect(disconnectedOriginal).toBe(0);
expect(disconnectedRemote1).toBe(0);
expect(disconnectedRemote2).toBe(1);
await Promise.all([
utils.waitEvent(remoteBrowser1, 'disconnected'),
utils.waitEvent(originalBrowser, 'disconnected'),
originalBrowser.close(),
]);
expect(disconnectedOriginal).toBe(1);
expect(disconnectedRemote1).toBe(1);
expect(disconnectedRemote2).toBe(1);
});
});
};
module.exports.addPageTests = function({testRunner, expect}) {

View File

@ -50,14 +50,14 @@ module.exports.addTests = function({testRunner, expect, playwright, defaultBrows
it('background_page target type should be available', async() => {
const browserWithExtension = await playwright.launch(extensionOptions);
const page = await browserWithExtension.newPage();
const backgroundPageTarget = await browserWithExtension.waitForTarget(target => target.type() === 'background_page');
const backgroundPageTarget = await browserWithExtension.chromium.waitForTarget(target => target.type() === 'background_page');
await page.close();
await browserWithExtension.close();
expect(backgroundPageTarget).toBeTruthy();
});
it('target.page() should return a background_page', async({}) => {
const browserWithExtension = await playwright.launch(extensionOptions);
const backgroundPageTarget = await browserWithExtension.waitForTarget(target => target.type() === 'background_page');
const backgroundPageTarget = await browserWithExtension.chromium.waitForTarget(target => target.type() === 'background_page');
const page = await backgroundPageTarget.page();
expect(await page.evaluate(() => 2 * 3)).toBe(6);
expect(await page.evaluate(() => window.MAGIC)).toBe(42);
@ -123,7 +123,7 @@ module.exports.addTests = function({testRunner, expect, playwright, defaultBrows
const context = await browser.createIncognitoBrowserContext();
await Promise.all([
context.newPage(),
context.waitForTarget(target => target.url().includes('devtools://')),
browser.chromium.waitForTarget(target => target.browserContext() === context && target.url().includes('devtools://')),
]);
await browser.close();
});

View File

@ -344,7 +344,7 @@ module.exports.addTests = function({testRunner, expect, defaultBrowserOptions, p
const browserOne = await playwright.launch(defaultBrowserOptions);
const browserTwo = await playwright.connect({ ...defaultBrowserOptions, browserWSEndpoint: browserOne.chromium.wsEndpoint() });
const [page1, page2] = await Promise.all([
new Promise(x => browserOne.once('targetcreated', target => x(target.page()))),
new Promise(x => browserOne.chromium.once('targetcreated', target => x(target.page()))),
browserTwo.newPage(),
]);
expect(await page1.evaluate(() => 7 * 8)).toBe(56);
@ -372,55 +372,4 @@ module.exports.addTests = function({testRunner, expect, defaultBrowserOptions, p
expect(Devices['iPhone 6']).toBe(playwright.devices['iPhone 6']);
});
});
describe.skip(WEBKIT)('Browser target events', function() {
it('should work', async({server}) => {
const browser = await playwright.launch(defaultBrowserOptions);
const events = [];
browser.on('targetcreated', () => events.push('CREATED'));
browser.on('targetchanged', () => events.push('CHANGED'));
browser.on('targetdestroyed', () => events.push('DESTROYED'));
const page = await browser.newPage();
await page.goto(server.EMPTY_PAGE);
await page.close();
expect(events).toEqual(['CREATED', 'CHANGED', 'DESTROYED']);
await browser.close();
});
});
describe.skip(WEBKIT || FFOX)('Browser.Events.disconnected', function() {
it('should be emitted when: browser gets closed, disconnected or underlying websocket gets closed', async() => {
const originalBrowser = await playwright.launch(defaultBrowserOptions);
const browserWSEndpoint = originalBrowser.chromium.wsEndpoint();
const remoteBrowser1 = await playwright.connect({browserWSEndpoint});
const remoteBrowser2 = await playwright.connect({browserWSEndpoint});
let disconnectedOriginal = 0;
let disconnectedRemote1 = 0;
let disconnectedRemote2 = 0;
originalBrowser.on('disconnected', () => ++disconnectedOriginal);
remoteBrowser1.on('disconnected', () => ++disconnectedRemote1);
remoteBrowser2.on('disconnected', () => ++disconnectedRemote2);
await Promise.all([
utils.waitEvent(remoteBrowser2, 'disconnected'),
remoteBrowser2.disconnect(),
]);
expect(disconnectedOriginal).toBe(0);
expect(disconnectedRemote1).toBe(0);
expect(disconnectedRemote2).toBe(1);
await Promise.all([
utils.waitEvent(remoteBrowser1, 'disconnected'),
utils.waitEvent(originalBrowser, 'disconnected'),
originalBrowser.close(),
]);
expect(disconnectedOriginal).toBe(1);
expect(disconnectedRemote1).toBe(1);
expect(disconnectedRemote2).toBe(1);
});
});
};

View File

@ -57,5 +57,5 @@ module.exports.addTests = function({testRunner, expect, defaultBrowserOptions, p
* @param {!Playwright.BrowserContext} context
*/
function oopifs(context) {
return context.targets().filter(target => target._targetInfo.type === 'iframe');
return context.browser().chromium.targets().filter(target => target._targetInfo.type === 'iframe');
}

View File

@ -289,9 +289,8 @@ module.exports.addTests = function({testRunner, expect, headless, playwright, FF
// 3. After that, remove the iframe.
frame.remove();
});
const popupTarget = page.browserContext().targets().find(target => target !== page.target());
// 4. Connect to the popup and make sure it doesn't throw.
await popupTarget.page();
await page.browserContext().pages();
});
});
@ -1145,8 +1144,8 @@ module.exports.addTests = function({testRunner, expect, headless, playwright, FF
// FIXME: WebKit shouldn't send targetDestroyed on PSON so that we could
// convert target destroy events into close.
describe('Page.Events.Close', function() {
it.skip(WEBKIT)('should work with window.close', async function({ page, context, server }) {
const newPagePromise = new Promise(fulfill => context.once('targetcreated', target => fulfill(target.page())));
it.skip(WEBKIT)('should work with window.close', async function({ browser, page, context, server }) {
const newPagePromise = new Promise(fulfill => browser.chromium.once('targetcreated', target => fulfill(target.page())));
await page.evaluate(() => window['newPage'] = window.open('about:blank'));
const newPage = await newPagePromise;
const closedPromise = new Promise(x => newPage.on('close', x));

View File

@ -153,7 +153,6 @@ module.exports.addTests = ({testRunner, product, playwrightPath}) => {
// FIXME: screenshot tests will fail when running in parallel (maybe)
require('./screenshot.spec.js').addTests(testOptions);
require('./queryselector.spec.js').addTests(testOptions);
require('./target.spec.js').addTests(testOptions);
require('./waittask.spec.js').addTests(testOptions);
if (CHROME) {
require('./CDPSession.spec.js').addTests(testOptions);
@ -162,6 +161,7 @@ module.exports.addTests = ({testRunner, product, playwrightPath}) => {
require('./chromiumonly.spec.js').addPageTests(testOptions);
require('../src/chromium/features/geolocation.spec.js').addTests(testOptions);
require('../src/chromium/features/pdf.spec.js').addTests(testOptions);
require('../src/chromium/features/chromium.spec.js').addTests(testOptions);
}
});

View File

@ -67,7 +67,7 @@ describe('Playwright-Web', () => {
});
it('should work over exposed DevTools protocol', async({browser, page, serverConfig}) => {
// Expose devtools protocol binding into page.
const session = await browser.chromium.createBrowserCDPSession();
const session = await browser.chromium.browserTarget().createCDPSession();
const pageInfo = (await session.send('Target.getTargets')).targetInfos.find(info => info.attached);
await session.send('Target.exposeDevToolsProtocol', {targetId: pageInfo.targetId});
await session.detach();