api(chromium): remove Target from public API (#1163)

This commit is contained in:
Yury Semikhatsky 2020-03-02 13:58:22 -08:00 committed by GitHub
parent f242e0c74f
commit a57978a5f0
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
13 changed files with 194 additions and 321 deletions

View File

@ -9,6 +9,7 @@
- [class: Browser](#class-browser)
- [class: BrowserContext](#class-browsercontext)
- [class: Page](#class-page)
- [class: PageEvent](#class-pageevent)
- [class: Frame](#class-frame)
- [class: ElementHandle](#class-elementhandle)
- [class: JSHandle](#class-jshandle)
@ -28,7 +29,6 @@
- [class: ChromiumBrowserContext](#class-chromiumbrowsercontext)
- [class: ChromiumCoverage](#class-chromiumcoverage)
- [class: ChromiumSession](#class-chromiumsession)
- [class: ChromiumTarget](#class-chromiumtarget)
- [class: FirefoxBrowser](#class-firefoxbrowser)
- [class: WebKitBrowser](#class-webkitbrowser)
- [Environment Variables](#environment-variables)
@ -265,6 +265,7 @@ await context.close();
<!-- GEN:toc -->
- [event: 'close'](#event-close)
- [event: 'page'](#event-page)
- [browserContext.addInitScript(script[, ...args])](#browsercontextaddinitscriptscript-args)
- [browserContext.clearCookies()](#browsercontextclearcookies)
- [browserContext.clearPermissions()](#browsercontextclearpermissions)
@ -287,6 +288,12 @@ Emitted when Browser context gets closed. This might happen because of one of th
- Browser application is closed or crashed.
- The [`browser.close`](#browserclose) method was called.
#### event: 'page'
- <[PageEvent]>
Emitted when a new Page is created in the BrowserContext. The event will also fire for popup
pages.
#### browserContext.addInitScript(script[, ...args])
- `script` <[function]|[string]|[Object]> Script to be evaluated in all pages in the browser context.
- `path` <[string]> Path to the JavaScript file. If `path` is a relative path, then it is resolved relative to [current working directory](https://nodejs.org/api/process.html#process_process_cwd).
@ -312,7 +319,6 @@ await browserContext.addInitScript(preloadFile);
```
> **NOTE** The order of evaluation of multiple scripts installed via [browserContext.addInitScript(script[, ...args])](#browsercontextaddinitscriptscript-args) and [page.addInitScript(script[, ...args])](#pageaddinitscriptscript-args) is not defined.
#### browserContext.clearCookies()
- returns: <[Promise]>
@ -361,7 +367,8 @@ If URLs are specified, only cookies that affect those URLs are returned.
Creates a new page in the browser context.
#### browserContext.pages()
- returns: <[Promise]<[Array]<[Page]>>> Promise which resolves to an array of all open pages. Non visible pages, such as `"background_page"`, will not be listed here. You can find them using [chromiumTarget.page()](#chromiumtargetpage).
- returns: <[Promise]<[Array]<[Page]>>> Promise which resolves to an array of all open pages. Non visible pages, such as `"background_page"`, will not be listed here. You can find them using
[chromiumBrowserContext.backgroundPages()](#chromiumbrowsercontextbackgroundpages).
An array of all pages inside the browser context.
@ -1662,6 +1669,13 @@ This method returns all of the dedicated [WebWorkers](https://developer.mozilla.
> **NOTE** This does not contain ServiceWorkers
### class: PageEvent
Event object passed to the listeners of ['page'](#event-page) on [`BrowserContext`](#class-browsercontext). Provides access
to the newly created page.
#### pageEvent.page()
- returns: <[Promise]<[Page]>> Promise which resolves to the created page.
### class: Frame
@ -3556,7 +3570,7 @@ await browser.stopTracing();
```
<!-- GEN:toc -->
- [chromiumBrowser.browserTarget()](#chromiumbrowserbrowsertarget)
- [chromiumBrowser.createBrowserSession()](#chromiumbrowsercreatebrowsersession)
- [chromiumBrowser.startTracing(page, [options])](#chromiumbrowserstarttracingpage-options)
- [chromiumBrowser.stopTracing()](#chromiumbrowserstoptracing)
<!-- GEN:stop -->
@ -3569,10 +3583,9 @@ await browser.stopTracing();
- [browser.newPage([options])](#browsernewpageoptions)
<!-- GEN:stop -->
#### chromiumBrowser.browserTarget()
- returns: <[ChromiumTarget]>
Returns browser target.
#### chromiumBrowser.createBrowserSession()
- returns: <[Promise]<[ChromiumSession]>> Promise that resolves to the newly created browser
session.
#### chromiumBrowser.startTracing(page, [options])
- `page` <[Page]> Optional, if specified, tracing includes screenshots of the given page.
@ -3599,15 +3612,14 @@ const backgroundPage = await backroundPageTarget.page();
```
<!-- GEN:toc -->
- [event: 'targetchanged'](#event-targetchanged)
- [event: 'targetcreated'](#event-targetcreated)
- [event: 'targetdestroyed'](#event-targetdestroyed)
- [chromiumBrowserContext.pageTarget(page)](#chromiumbrowsercontextpagetargetpage)
- [chromiumBrowserContext.targets()](#chromiumbrowsercontexttargets)
- [chromiumBrowserContext.waitForTarget(predicate[, options])](#chromiumbrowsercontextwaitfortargetpredicate-options)
- [event: 'backgroundpage'](#event-backgroundpage)
- [event: 'serviceworker'](#event-serviceworker)
- [chromiumBrowserContext.backgroundPages()](#chromiumbrowsercontextbackgroundpages)
- [chromiumBrowserContext.createSession(page)](#chromiumbrowsercontextcreatesessionpage)
<!-- GEN:stop -->
<!-- GEN:toc-extends-BrowserContext -->
- [event: 'close'](#event-close)
- [event: 'page'](#event-page)
- [browserContext.addInitScript(script[, ...args])](#browsercontextaddinitscriptscript-args)
- [browserContext.clearCookies()](#browsercontextclearcookies)
- [browserContext.clearPermissions()](#browsercontextclearpermissions)
@ -3623,50 +3635,24 @@ const backgroundPage = await backroundPageTarget.page();
- [browserContext.setPermissions(origin, permissions[])](#browsercontextsetpermissionsorigin-permissions)
<!-- GEN:stop -->
#### event: 'targetchanged'
- <[ChromiumTarget]>
#### event: 'backgroundpage'
- <[PageEvent]>
Emitted when the url of a target changes.
Emitted when new background page is created in the context.
> **NOTE** Only includes targets from this browser context.
> **NOTE** Only works with persistent context.
#### event: 'serviceworker'
- <[Worker]>
#### event: 'targetcreated'
- <[ChromiumTarget]>
Emitted when new service worker is created in the context.
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 [`browserContext.newPage`](#browsercontextnewpage).
#### chromiumBrowserContext.backgroundPages()
- returns: <[Promise]<[Array]<[Page]>>> Promise which resolves to an array of all existing background pages in the context.
> **NOTE** Only includes targets from this browser context.
#### event: 'targetdestroyed'
- <[ChromiumTarget]>
Emitted when a target is destroyed, for example when a page is closed.
> **NOTE** Only includes targets from this browser context.
#### chromiumBrowserContext.pageTarget(page)
- `page` <[Page]> Page to return target for.
- returns: <[ChromiumTarget]> a target given page was created from.
#### chromiumBrowserContext.targets()
- returns: <[Array]<[ChromiumTarget]>>
An array of all active targets inside the browser context.
#### chromiumBrowserContext.waitForTarget(predicate[, options])
- `predicate` <[function]\([ChromiumTarget]\):[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]<[ChromiumTarget]>> Promise which resolves to the first target found that matches the `predicate` function.
This searches for a target in the 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 page.context().waitForTarget(target => target.url() === 'https://www.example.com/');
```
#### chromiumBrowserContext.createSession(page)
- `page` <[Page]> Page to create new session for.
- returns: <[Promise]<[ChromiumSession]>> Promise that resolves to the newly created session.
### class: ChromiumCoverage
@ -3737,7 +3723,7 @@ reported.
* extends: [EventEmitter](https://nodejs.org/api/events.html#events_class_eventemitter)
The `CDPSession` instances are used to talk raw Chrome Devtools Protocol:
The `ChromiumSession` instances are used to talk raw Chrome Devtools Protocol:
- protocol methods can be called with `session.send` method.
- protocol events can be subscribed to with `session.on` method.
@ -3746,7 +3732,7 @@ Useful links:
- Getting Started with DevTools Protocol: https://github.com/aslushnikov/getting-started-with-cdp/blob/master/README.md
```js
const client = await chromium.pageTarget(page).createCDPSession();
const client = await page.context().createSession(page);
await client.send('Animation.enable');
client.on('Animation.animationCreated', () => console.log('Animation created!'));
const response = await client.send('Animation.getPlaybackRate');
@ -3764,7 +3750,7 @@ await client.send('Animation.setPlaybackRate', {
#### chromiumSession.detach()
- returns: <[Promise]>
Detaches the cdpSession from the target. Once detached, the cdpSession object won't emit any events and can't be used
Detaches the chromiumSession from the target. Once detached, the chromiumSession object won't emit any events and can't be used
to send messages.
#### chromiumSession.send(method[, params])
@ -3772,53 +3758,6 @@ to send messages.
- `params` <[Object]> Optional method parameters
- returns: <[Promise]<[Object]>>
### class: ChromiumTarget
<!-- GEN:toc -->
- [chromiumTarget.context()](#chromiumtargetcontext)
- [chromiumTarget.createCDPSession()](#chromiumtargetcreatecdpsession)
- [chromiumTarget.opener()](#chromiumtargetopener)
- [chromiumTarget.page()](#chromiumtargetpage)
- [chromiumTarget.serviceWorker()](#chromiumtargetserviceworker)
- [chromiumTarget.type()](#chromiumtargettype)
- [chromiumTarget.url()](#chromiumtargeturl)
<!-- GEN:stop -->
#### chromiumTarget.context()
- returns: <[BrowserContext]>
The browser context the target belongs to.
#### chromiumTarget.createCDPSession()
- returns: <[Promise]<[CDPSession]>>
Creates a Chrome Devtools Protocol session attached to the target.
#### chromiumTarget.opener()
- returns: <?[ChromiumTarget]>
Get the target that opened this target. Top-level targets return `null`.
#### chromiumTarget.page()
- returns: <[Promise]<?[Page]>>
If the target is not of type `"page"` or `"background_page"`, returns `null`.
#### chromiumTarget.serviceWorker()
- returns: <[Promise]<?[Worker]>>
Attaches to the service worker target. If the target is not of type `"service_worker"`, returns `null`.
#### chromiumTarget.type()
- returns: <"page"|"background_page"|"service_worker"|"shared_worker"|"other"|"browser">
Identifies what kind of target this is. Can be `"page"`, [`"background_page"`](https://developer.chrome.com/extensions/background_pages), `"service_worker"`, `"shared_worker"`, `"browser"` or `"other"`.
#### chromiumTarget.url()
- returns: <[string]>
### class: FirefoxBrowser
* extends: [Browser]
@ -3908,18 +3847,18 @@ const { chromium } = require('playwright');
(async () => {
const pathToExtension = require('path').join(__dirname, 'my-extension');
const browser = await chromium.launch({
const userDataDir = '/tmp/test-user-data-dir';
const browserContext = await chromium.launchPersistent(userDataDir,{
headless: false,
args: [
`--disable-extensions-except=${pathToExtension}`,
`--load-extension=${pathToExtension}`
]
});
const targets = await browser.targets();
const backgroundPageTarget = targets.find(target => target.type() === 'background_page');
const backgroundPage = await backgroundPageTarget.page();
const backgroundPages = await browserContext.backgroundPages();
const backgroundPage = backgroundPages[0];
// Test the background page as you would any other page.
await browser.close();
await browserContext.close();
})();
```
@ -3939,7 +3878,6 @@ const { chromium } = require('playwright');
[ChromiumBrowser]: #class-chromiumbrowser "ChromiumBrowser"
[ChromiumBrowserContext]: #class-chromiumbrowsercontext "ChromiumBrowserContext"
[ChromiumSession]: #class-chromiumsession "ChromiumSession"
[ChromiumTarget]: #class-chromiumtarget "ChromiumTarget"
[ConsoleMessage]: #class-consolemessage "ConsoleMessage"
[Coverage]: #class-coverage "Coverage"
[Dialog]: #class-dialog "Dialog"
@ -3956,6 +3894,7 @@ const { chromium } = require('playwright');
[Mouse]: #class-mouse "Mouse"
[Object]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object "Object"
[Page]: #class-page "Page"
[PageEvent]: #class-page "PageEvent"
[Playwright]: #class-playwright "Playwright"
[Promise]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise "Promise"
[RegExp]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/RegExp

View File

@ -25,14 +25,13 @@ export { Frame } from './frames';
export { Keyboard, Mouse } from './input';
export { JSHandle } from './javascript';
export { Request, Response } from './network';
export { FileChooser, Page, Worker } from './page';
export { FileChooser, Page, PageEvent, Worker } from './page';
export { Selectors } from './selectors';
export { CRBrowser as ChromiumBrowser } from './chromium/crBrowser';
export { CRBrowserContext as ChromiumBrowserContext } from './chromium/crBrowser';
export { CRCoverage as ChromiumCoverage } from './chromium/crCoverage';
export { CRSession as ChromiumSession } from './chromium/crConnection';
export { CRTarget as ChromiumTarget } from './chromium/crTarget';
export { FFBrowser as FirefoxBrowser } from './firefox/ffBrowser';

View File

@ -17,10 +17,10 @@
import { Events } from './events';
import { Events as CommonEvents } from '../events';
import { assert, helper } from '../helper';
import { assert, helper, debugError } from '../helper';
import { BrowserContext, BrowserContextOptions, validateBrowserContextOptions, assertBrowserContextIsNotOwned, verifyGeolocation } from '../browserContext';
import { CRConnection, ConnectionEvents, CRSession } from './crConnection';
import { Page } from '../page';
import { Page, PageEvent } from '../page';
import { CRTarget } from './crTarget';
import { Protocol } from './protocol';
import { CRPage } from './crPage';
@ -92,8 +92,30 @@ export class CRBrowser extends platform.EventEmitter implements Browser {
assert(!this._targets.has(event.targetInfo.targetId), 'Target should not exist before targetCreated');
this._targets.set(event.targetInfo.targetId, target);
if (target._isInitialized || await target._initializedPromise)
context.emit(Events.CRBrowserContext.TargetCreated, target);
try {
switch (targetInfo.type) {
case 'page': {
const page = await target.page();
const event = new PageEvent(page!);
context.emit(CommonEvents.BrowserContext.PageEvent, event);
break;
}
case 'background_page': {
const page = await target.page();
const event = new PageEvent(page!);
context.emit(Events.CRBrowserContext.BackgroundPage, event);
break;
}
case 'service_worker': {
const serviceWorker = await target.serviceWorker();
context.emit(Events.CRBrowserContext.ServiceWorker, serviceWorker);
break;
}
}
} catch (e) {
// Do not dispatch the event if initialization failed.
debugError(e);
}
}
async _targetDestroyed(event: { targetId: string; }) {
@ -101,18 +123,12 @@ export class CRBrowser extends platform.EventEmitter implements Browser {
target._initializedCallback(false);
this._targets.delete(event.targetId);
target._didClose();
if (await target._initializedPromise)
target.context().emit(Events.CRBrowserContext.TargetDestroyed, target);
}
_targetInfoChanged(event: Protocol.Target.targetInfoChangedPayload) {
const target = this._targets.get(event.targetInfo.targetId)!;
assert(target, 'target should exist before targetInfoChanged');
const previousURL = target.url();
const wasInitialized = target._isInitialized;
target._targetInfoChanged(event.targetInfo);
if (wasInitialized && previousURL !== target.url())
target.context().emit(Events.CRBrowserContext.TargetChanged, target);
}
async _closePage(page: Page) {
@ -130,8 +146,8 @@ export class CRBrowser extends platform.EventEmitter implements Browser {
await disconnected;
}
browserTarget(): CRTarget {
return [...this._targets.values()].find(t => t.type() === 'browser')!;
async createBrowserSession(): Promise<CRSession> {
return await this._connection.createBrowserSession();
}
async startTracing(page: Page | undefined, options: { path?: string; screenshots?: boolean; categories?: string[]; } = {}) {
@ -319,36 +335,14 @@ export class CRBrowserContext extends platform.EventEmitter implements BrowserCo
this.emit(CommonEvents.BrowserContext.Close);
}
pageTarget(page: Page): CRTarget {
return CRTarget.fromPage(page);
async backgroundPages(): Promise<Page[]> {
const targets = this._browser._allTargets().filter(target => target.context() === this && target.type() === 'background_page');
const pages = await Promise.all(targets.map(target => target.page()));
return pages.filter(page => !!page) as Page[];
}
targets(): CRTarget[] {
return this._browser._allTargets().filter(t => t.context() === this);
}
async waitForTarget(predicate: (arg0: CRTarget) => boolean, options: { timeout?: number; } = {}): Promise<CRTarget> {
const { timeout = 30000 } = options;
const existingTarget = this._browser._allTargets().find(predicate);
if (existingTarget)
return existingTarget;
let resolve: (target: CRTarget) => void;
const targetPromise = new Promise<CRTarget>(x => resolve = x);
this.on(Events.CRBrowserContext.TargetCreated, check);
this.on(Events.CRBrowserContext.TargetChanged, check);
try {
if (!timeout)
return await targetPromise;
return await helper.waitWithTimeout(targetPromise, 'target', timeout);
} finally {
this.removeListener(Events.CRBrowserContext.TargetCreated, check);
this.removeListener(Events.CRBrowserContext.TargetChanged, check);
}
function check(target: CRTarget) {
if (predicate(target))
resolve(target);
}
async createSession(page: Page): Promise<CRSession> {
return CRTarget.fromPage(page).sessionFactory();
}
_browserClosed() {

View File

@ -31,7 +31,7 @@ export class CRTarget {
private readonly _browser: CRBrowser;
private readonly _browserContext: CRBrowserContext;
readonly _targetId: string;
private _sessionFactory: () => Promise<CRSession>;
readonly sessionFactory: () => Promise<CRSession>;
private _pagePromise: Promise<Page> | null = null;
_crPage: CRPage | null = null;
private _workerPromise: Promise<Worker> | null = null;
@ -52,7 +52,7 @@ export class CRTarget {
this._browser = browser;
this._browserContext = browserContext;
this._targetId = targetInfo.targetId;
this._sessionFactory = sessionFactory;
this.sessionFactory = sessionFactory;
this._initializedPromise = new Promise(fulfill => this._initializedCallback = fulfill).then(async success => {
if (!success)
return false;
@ -78,7 +78,7 @@ export class CRTarget {
async page(): Promise<Page | null> {
if ((this._targetInfo.type === 'page' || this._targetInfo.type === 'background_page') && !this._pagePromise) {
this._pagePromise = this._sessionFactory().then(async client => {
this._pagePromise = this.sessionFactory().then(async client => {
this._crPage = new CRPage(client, this._browser, this._browserContext);
const page = this._crPage.page();
(page as any)[targetSymbol] = this;
@ -95,7 +95,7 @@ export class CRTarget {
return null;
if (!this._workerPromise) {
// TODO(einbinder): Make workers send their console logs.
this._workerPromise = this._sessionFactory().then(session => {
this._workerPromise = this.sessionFactory().then(session => {
const worker = new Worker(this._targetInfo.url);
session.once('Runtime.executionContextCreated', async event => {
worker._createExecutionContext(new CRExecutionContext(session, event.context));
@ -130,10 +130,6 @@ export class CRTarget {
return this._browser._targets.get(openerId)!;
}
createCDPSession(): Promise<CRSession> {
return this._sessionFactory();
}
_targetInfoChanged(targetInfo: Protocol.Target.TargetInfo) {
this._targetInfo = targetInfo;

View File

@ -17,8 +17,7 @@
export const Events = {
CRBrowserContext: {
TargetCreated: 'targetcreated',
TargetDestroyed: 'targetdestroyed',
TargetChanged: 'targetchanged',
BackgroundPage: 'backgroundpage',
ServiceWorker: 'serviceworker',
}
};

View File

@ -21,7 +21,8 @@ export const Events = {
},
BrowserContext: {
Close: 'close'
Close: 'close',
PageEvent: 'page',
},
BrowserServer: {

View File

@ -93,6 +93,18 @@ export type FileChooser = {
multiple: boolean
};
export class PageEvent {
private readonly _page: Page;
constructor(page: Page) {
this._page = page;
}
async page(/* options?: frames.NavigateOptions */): Promise<Page> {
return this._page;
}
}
export class Page extends platform.EventEmitter {
private _closed = false;
private _closedCallback: () => void;

View File

@ -70,7 +70,8 @@ export class Chromium implements BrowserType {
const { timeout = 30000 } = options || {};
const { browserServer, transport } = await this._launchServer(options, 'persistent', userDataDir);
const browser = await CRBrowser.connect(transport!);
await helper.waitWithTimeout(browser._defaultContext.waitForTarget(t => t.type() === 'page'), 'first page', timeout);
const firstPage = new Promise(r => browser._defaultContext.once(Events.BrowserContext.PageEvent, r));
await helper.waitWithTimeout(firstPage, 'first page', timeout);
// Hack: for typical launch scenario, ensure that close waits for actual process termination.
const browserContext = browser._defaultContext;
browserContext.close = () => browserServer.close();

View File

@ -24,19 +24,8 @@ module.exports.describe = function({testRunner, expect, playwright, FFOX, CHROMI
const {it, fit, xit, dit} = testRunner;
const {beforeAll, beforeEach, afterAll, afterEach} = testRunner;
describe('Target', function() {
it('ChromiumBrowserContext.targets should return all of the targets', async({page, server, browser}) => {
const second = await page.context().newPage();
await second.goto(server.EMPTY_PAGE);
const targets = page.context().targets();
// The pages will be the testing page from the harness and the one created here.
expect(targets.length).toBe(2);
expect(targets.some(target => target.type() !== 'page')).toBe(false);
expect(targets.some(target => target.url() === 'about:blank')).toBeTruthy('Missing blank page');
expect(targets.some(target => target.url() === server.EMPTY_PAGE)).toBeTruthy('Missing new page');
await second.close();
});
it('BrowserContext.pages should return all of the pages', async({page, server, context}) => {
describe('BrowserContext', function() {
it('pages() should return all of the pages', async({page, server, context}) => {
const second = await page.context().newPage();
const allPages = await context.pages();
expect(allPages.length).toBe(2);
@ -44,12 +33,9 @@ module.exports.describe = function({testRunner, expect, playwright, FFOX, CHROMI
expect(allPages).toContain(second);
await second.close();
});
it('should report browser target', async({browser}) => {
expect(browser.browserTarget()).toBeTruthy();
});
it('should report when a new page is created and closed', async({browser, page, server, context}) => {
const [otherPage] = await Promise.all([
page.context().waitForTarget(target => target.url() === server.CROSS_PROCESS_PREFIX + '/empty.html').then(target => target.page()),
new Promise(r => context.once('page', async event => r(await event.page()))),
page.evaluate(url => window.open(url), server.CROSS_PROCESS_PREFIX + '/empty.html'),
]);
expect(otherPage.url()).toContain(server.CROSS_PROCESS_PREFIX);
@ -60,83 +46,55 @@ module.exports.describe = function({testRunner, expect, playwright, FFOX, CHROMI
expect(allPages).toContain(page);
expect(allPages).toContain(otherPage);
const closePagePromise = new Promise(fulfill => page.context().once('targetdestroyed', target => fulfill(target.page())));
let closeEventReceived;
otherPage.once('close', () => closeEventReceived = true);
await otherPage.close();
expect(await closePagePromise).toBe(otherPage);
expect(closeEventReceived).toBeTruthy();
allPages = await Promise.all(page.context().targets().map(target => target.page()));
allPages = await context.pages();
expect(allPages).toContain(page);
expect(allPages).not.toContain(otherPage);
});
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 => page.context().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 => page.context().once('targetdestroyed', target => fulfill(target)));
await page.evaluate(() => window.registrationPromise.then(registration => registration.unregister()));
expect(await destroyedTarget).toBe(await createdTarget);
});
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 page.context().waitForTarget(target => target.type() === 'service_worker');
const worker = await target.serviceWorker();
const [worker] = await Promise.all([
new Promise(fulfill => context.once('serviceworker', fulfill)),
page.goto(server.PREFIX + '/serviceworkers/empty/sw.html')
]);
expect(await worker.evaluate(() => self.toString())).toBe('[object ServiceWorkerGlobalScope]');
});
it('should not create a worker from a shared worker', async({browser, page, server, context}) => {
await page.goto(server.EMPTY_PAGE);
let serviceWorkerCreated;
context.once('serviceworker', () => serviceWorkerCreated = true);
await page.evaluate(() => {
new SharedWorker('data:text/javascript,console.log("hi")');
});
const target = await page.context().waitForTarget(target => target.type() === 'shared_worker');
const worker = await target.serviceWorker();
expect(worker).toBe(null);
expect(serviceWorkerCreated).not.toBeTruthy();
});
it('should report when a target url changes', async({browser, page, server, context}) => {
await page.goto(server.EMPTY_PAGE);
let changedTarget = new Promise(fulfill => page.context().once('targetchanged', target => fulfill(target)));
await page.goto(server.CROSS_PROCESS_PREFIX + '/');
expect((await changedTarget).url()).toBe(server.CROSS_PROCESS_PREFIX + '/');
it('should not report uninitialized pages', async({browser, context}) => {
const pagePromise = new Promise(fulfill => context.once('page', async event => fulfill(await event.page())));
context.newPage();
const newPage = await pagePromise;
expect(newPage.url()).toBe('about:blank');
changedTarget = new Promise(fulfill => page.context().once('targetchanged', target => fulfill(target)));
await page.goto(server.EMPTY_PAGE);
expect((await changedTarget).url()).toBe(server.EMPTY_PAGE);
});
it('should not report uninitialized pages', async({browser, page, server, context}) => {
let targetChanged = false;
const listener = () => targetChanged = true;
browser.on('targetchanged', listener);
const targetPromise = new Promise(fulfill => context.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 popupPromise = new Promise(fulfill => context.once('page', async event => fulfill(await event.page())));
const evaluatePromise = newPage.evaluate(() => window.open('about:blank'));
const target2 = await targetPromise2;
expect(target2.url()).toBe('about:blank');
const popup = await popupPromise;
expect(popup.url()).toBe('about:blank');
await evaluatePromise;
await newPage.close();
expect(targetChanged).toBe(false, 'target should not be reported as changed');
browser.removeListener('targetchanged', listener);
});
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.
await Promise.all([
const [newPage] = await Promise.all([
new Promise(fulfill => context.once('page', async event => fulfill(await event.page()))),
page.evaluate(url => window.open(url), server.PREFIX + '/one-style.html'),
server.waitForRequest('/one-style.css')
]);
// Connect to the opened page.
const target = await page.context().waitForTarget(target => target.url().includes('one-style.html'));
const newPage = await target.page();
expect(newPage.url()).toBe(server.PREFIX + '/one-style.html');
// Issue a redirect.
serverResponse.writeHead(302, { location: '/injectedstyle.css' });
serverResponse.end();
@ -147,66 +105,36 @@ module.exports.describe = function({testRunner, expect, playwright, FFOX, CHROMI
});
it('should have an opener', async({browser, page, server, context}) => {
await page.goto(server.EMPTY_PAGE);
const [createdTarget] = await Promise.all([
new Promise(fulfill => page.context().once('targetcreated', target => fulfill(target))),
const [popup] = await Promise.all([
new Promise(fulfill => context.once('page', async event => fulfill(await event.page()))),
page.goto(server.PREFIX + '/popup/window-open.html')
]);
expect((await createdTarget.page()).url()).toBe(server.PREFIX + '/popup/popup.html');
expect(createdTarget.opener()).toBe(page.context().pageTarget(page));
expect(page.context().pageTarget(page).opener()).toBe(null);
await popup.waitForLoadState();
expect(popup.url()).toBe(server.PREFIX + '/popup/popup.html');
expect(await popup.opener()).toBe(page);
expect(await page.opener()).toBe(null);
});
it('should close all belonging targets once closing context', async function({browser}) {
const context = await browser.newContext();
await context.newPage();
expect((await context.targets()).length).toBe(1);
expect((await context.pages()).length).toBe(1);
await context.close();
expect((await context.targets()).length).toBe(0);
expect((await context.pages()).length).toBe(0);
});
});
describe('Chromium.waitForTarget', () => {
it('should wait for a target', async function({server, browser}) {
const context = await browser.newContext();
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, context, server}) {
const error = await context.waitForTarget(target => target.url() === server.EMPTY_PAGE, {timeout: 1}).catch(e => e);
expect(error).toBeInstanceOf(playwright.errors.TimeoutError);
});
it('should wait for a target', async function({browser, server}) {
const context = await browser.newContext();
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 fire target events', async function({browser, server}) {
it('should fire page lifecycle events', async function({browser, server}) {
const context = await browser.newContext();
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()));
context.on('page', async event => {
const page = await event.page();
events.push('CREATED: ' + page.url());
page.on('close', () => events.push('DESTROYED: ' + page.url()))
});
const page = await context.newPage();
await page.goto(server.EMPTY_PAGE);
await page.close();
expect(events).toEqual([
'CREATED: about:blank',
`CHANGED: ${server.EMPTY_PAGE}`,
`DESTROYED: ${server.EMPTY_PAGE}`
]);
await context.close();

View File

@ -18,6 +18,7 @@ const path = require('path');
const os = require('os');
const fs = require('fs');
const util = require('util');
const { makeUserDataDir, removeUserDataDir } = require('../utils');
const rmAsync = util.promisify(require('rimraf'));
const mkdtempAsync = util.promisify(fs.mkdtemp);
@ -48,22 +49,17 @@ module.exports.describe = function({testRunner, expect, playwright, defaultBrows
});
describe('ChromiumHeadful', function() {
it('background_page target type should be available', async() => {
const browserWithExtension = await playwright.launch(extensionOptions);
const page = await browserWithExtension.newPage();
const backgroundPageTarget = await page.context().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 page = await browserWithExtension.newPage();
const backgroundPageTarget = await page.context().waitForTarget(target => target.type() === 'background_page');
const backgroundPage = await backgroundPageTarget.page();
expect(await backgroundPage.evaluate(() => 2 * 3)).toBe(6);
expect(await backgroundPage.evaluate(() => window.MAGIC)).toBe(42);
await browserWithExtension.close();
it('Context.backgroundPages should return a background pages', async() => {
const userDataDir = await makeUserDataDir();
const context = await playwright.launchPersistent(userDataDir, extensionOptions);
const backgroundPages = await context.backgroundPages();
let backgroundPage = backgroundPages.length
? backgroundPages[0]
: await new Promise(fulfill => context.once('backgroundpage', async event => fulfill(await event.page())));
expect(backgroundPage).toBeTruthy();
expect(await context.backgroundPages()).toContain(backgroundPage);
expect(await context.pages()).not.toContain(backgroundPage);
await removeUserDataDir(userDataDir);
});
// TODO: Support OOOPIF. @see https://github.com/GoogleChrome/puppeteer/issues/2548
xit('OOPIF: should report google.com frame', async({server}) => {
@ -90,9 +86,15 @@ module.exports.describe = function({testRunner, expect, playwright, defaultBrows
it('should open devtools when "devtools: true" option is given', async({server}) => {
const browser = await playwright.launch(Object.assign({devtools: true}, headfulOptions));
const context = await browser.newContext();
const browserSession = await browser.createBrowserSession();
await browserSession.send('Target.setDiscoverTargets', { discover: true });
const devtoolsPagePromise = new Promise(fulfill => browserSession.on('Target.targetCreated', async ({targetInfo}) => {
if (targetInfo.type === 'other' && targetInfo.url.includes('devtools://'))
fulfill();
}));
await Promise.all([
context.newPage(),
context.waitForTarget(target => target.url().includes('devtools://')),
devtoolsPagePromise,
context.newPage()
]);
await browser.close();
});

View File

@ -54,22 +54,6 @@ module.exports.describe = function({testRunner, expect, defaultBrowserOptions, p
});
});
describe('Browser target events', function() {
it('should work', async({server}) => {
const browser = await playwright.launch(defaultBrowserOptions);
const context = await browser.newContext();
const events = [];
context.on('targetcreated', target => events.push('CREATED'));
context.on('targetchanged', target => events.push('CHANGED'));
context.on('targetdestroyed', target => events.push('DESTROYED'));
const page = await context.newPage();
await page.goto(server.EMPTY_PAGE);
await page.close();
expect(events).toEqual(['CREATED', 'CHANGED', 'DESTROYED']);
await browser.close();
});
});
describe('BrowserFetcher', function() {
it('should download and extract linux binary', async({server}) => {
const downloadsFolder = await mkdtempAsync(TMP_FOLDER);

View File

@ -42,19 +42,29 @@ module.exports.describe = function({testRunner, expect, defaultBrowserOptions, p
state.browser = null;
});
xit('should report oopif frames', async function({browser, page, server, context}) {
const browserSession = await browser.createBrowserSession();
await browserSession.send('Target.setDiscoverTargets', { discover: true });
const oopifs = [];
browserSession.on('Target.targetCreated', async ({targetInfo}) => {
if (targetInfo.type === 'iframe')
oopifs.push(targetInfo);
});
await page.goto(server.PREFIX + '/dynamic-oopif.html');
expect(oopifs(page.context()).length).toBe(1);
expect(oopifs.length).toBe(1);
expect(page.frames().length).toBe(2);
});
it('should load oopif iframes with subresources and request interception', async function({browser, page, server, context}) {
await page.route('*', request => request.continue());
const browserSession = await browser.createBrowserSession();
await browserSession.send('Target.setDiscoverTargets', { discover: true });
const oopifs = [];
browserSession.on('Target.targetCreated', async ({targetInfo}) => {
if (targetInfo.type === 'iframe')
oopifs.push(targetInfo);
});
await page.goto(server.PREFIX + '/dynamic-oopif.html');
expect(oopifs(page.context()).length).toBe(1);
expect(oopifs.length).toBe(1);
await browserSession.detach();
});
});
};
function oopifs(context) {
return context.targets().filter(target => target._targetInfo.type === 'iframe');
}

View File

@ -24,9 +24,9 @@ module.exports.describe = function({testRunner, expect, FFOX, CHROMIUM, WEBKIT})
const {it, fit, xit, dit} = testRunner;
const {beforeAll, beforeEach, afterAll, afterEach} = testRunner;
describe('Chromium.createCDPSession', function() {
describe('ChromiumBrowserContext.createSession', function() {
it('should work', async function({page, browser, server}) {
const client = await page.context().pageTarget(page).createCDPSession();
const client = await page.context().createSession(page);
await Promise.all([
client.send('Runtime.enable'),
@ -36,7 +36,7 @@ module.exports.describe = function({testRunner, expect, FFOX, CHROMIUM, WEBKIT})
expect(foo).toBe('bar');
});
it('should send events', async function({page, browser, server}) {
const client = await page.context().pageTarget(page).createCDPSession();
const client = await page.context().createSession(page);
await client.send('Network.enable');
const events = [];
client.on('Network.requestWillBeSent', event => events.push(event));
@ -44,7 +44,7 @@ module.exports.describe = function({testRunner, expect, FFOX, CHROMIUM, WEBKIT})
expect(events.length).toBe(1);
});
it('should enable and disable domains independently', async function({page, browser, server}) {
const client = await page.context().pageTarget(page).createCDPSession();
const client = await page.context().createSession(page);
await client.send('Runtime.enable');
await client.send('Debugger.enable');
// JS coverage enables and then disables Debugger domain.
@ -59,7 +59,7 @@ module.exports.describe = function({testRunner, expect, FFOX, CHROMIUM, WEBKIT})
expect(event.url).toBe('foo.js');
});
it('should be able to detach session', async function({page, browser, server}) {
const client = await page.context().pageTarget(page).createCDPSession();
const client = await page.context().createSession(page);
await client.send('Runtime.enable');
const evalResponse = await client.send('Runtime.evaluate', {expression: '1 + 2', returnByValue: true});
expect(evalResponse.result.value).toBe(3);
@ -73,7 +73,7 @@ module.exports.describe = function({testRunner, expect, FFOX, CHROMIUM, WEBKIT})
expect(error.message).toContain('Session closed.');
});
it('should throw nice errors', async function({page, browser}) {
const client = await page.context().pageTarget(page).createCDPSession();
const client = await page.context().createSession(page);
const error = await theSourceOfTheProblems().catch(error => error);
expect(error.stack).toContain('theSourceOfTheProblems');
expect(error.message).toContain('ThisCommand.DoesNotExist');
@ -83,4 +83,12 @@ module.exports.describe = function({testRunner, expect, FFOX, CHROMIUM, WEBKIT})
}
});
});
describe('ChromiumBrowser.createBrowserSession', function() {
it('should work', async function({page, browser, server}) {
const session = await browser.createBrowserSession();
const version = await session.send('Browser.getVersion');
expect(version.userAgent).toBeTruthy();
await session.detach();
});
});
};