From a57978a5f05246dfd08b257cc85fed7e6a86c8da Mon Sep 17 00:00:00 2001 From: Yury Semikhatsky Date: Mon, 2 Mar 2020 13:58:22 -0800 Subject: [PATCH] api(chromium): remove Target from public API (#1163) --- docs/api.md | 155 ++++++++++----------------------- src/api.ts | 3 +- src/chromium/crBrowser.ts | 74 ++++++++-------- src/chromium/crTarget.ts | 12 +-- src/chromium/events.ts | 5 +- src/events.ts | 3 +- src/page.ts | 12 +++ src/server/chromium.ts | 3 +- test/chromium/chromium.spec.js | 148 ++++++++----------------------- test/chromium/headful.spec.js | 38 ++++---- test/chromium/launcher.spec.js | 16 ---- test/chromium/oopif.spec.js | 26 ++++-- test/chromium/session.spec.js | 20 +++-- 13 files changed, 194 insertions(+), 321 deletions(-) diff --git a/docs/api.md b/docs/api.md index f8d11fc5a0e54..83f83676e0f36 100644 --- a/docs/api.md +++ b/docs/api.md @@ -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(); - [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(); ``` -- [chromiumBrowser.browserTarget()](#chromiumbrowserbrowsertarget) +- [chromiumBrowser.createBrowserSession()](#chromiumbrowsercreatebrowsersession) - [chromiumBrowser.startTracing(page, [options])](#chromiumbrowserstarttracingpage-options) - [chromiumBrowser.stopTracing()](#chromiumbrowserstoptracing) @@ -3569,10 +3583,9 @@ await browser.stopTracing(); - [browser.newPage([options])](#browsernewpageoptions) -#### 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(); ``` -- [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) - [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) -#### event: 'targetchanged' -- <[ChromiumTarget]> - -Emitted when the url of a target changes. - -> **NOTE** Only includes targets from this browser context. - - -#### event: 'targetcreated' -- <[ChromiumTarget]> - -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). +#### event: 'backgroundpage' +- <[PageEvent]> -> **NOTE** Only includes targets from this browser context. +Emitted when new background page is created in the context. -#### event: 'targetdestroyed' -- <[ChromiumTarget]> +> **NOTE** Only works with persistent context. -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. +#### event: 'serviceworker' +- <[Worker]> -#### 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. +Emitted when new service worker is created in the context. -This searches for a target in the browser context. +#### chromiumBrowserContext.backgroundPages() +- returns: <[Promise]<[Array]<[Page]>>> Promise which resolves to an array of all existing background pages in the 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 - - - -- [chromiumTarget.context()](#chromiumtargetcontext) -- [chromiumTarget.createCDPSession()](#chromiumtargetcreatecdpsession) -- [chromiumTarget.opener()](#chromiumtargetopener) -- [chromiumTarget.page()](#chromiumtargetpage) -- [chromiumTarget.serviceWorker()](#chromiumtargetserviceworker) -- [chromiumTarget.type()](#chromiumtargettype) -- [chromiumTarget.url()](#chromiumtargeturl) - - -#### 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: - -Get the target that opened this target. Top-level targets return `null`. - -#### chromiumTarget.page() -- returns: <[Promise]> - -If the target is not of type `"page"` or `"background_page"`, returns `null`. - -#### chromiumTarget.serviceWorker() -- returns: <[Promise]> - -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 diff --git a/src/api.ts b/src/api.ts index 694d54394b9fa..6fa58c6522365 100644 --- a/src/api.ts +++ b/src/api.ts @@ -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'; diff --git a/src/chromium/crBrowser.ts b/src/chromium/crBrowser.ts index b2abd4480f945..4fe7334d7c602 100644 --- a/src/chromium/crBrowser.ts +++ b/src/chromium/crBrowser.ts @@ -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 { + 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); - } - - targets(): CRTarget[] { - return this._browser._allTargets().filter(t => t.context() === this); + async backgroundPages(): Promise { + 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[]; } - async waitForTarget(predicate: (arg0: CRTarget) => boolean, options: { timeout?: number; } = {}): Promise { - const { timeout = 30000 } = options; - const existingTarget = this._browser._allTargets().find(predicate); - if (existingTarget) - return existingTarget; - let resolve: (target: CRTarget) => void; - const targetPromise = new Promise(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 { + return CRTarget.fromPage(page).sessionFactory(); } _browserClosed() { diff --git a/src/chromium/crTarget.ts b/src/chromium/crTarget.ts index a9fd11b76c60b..e7c150dd92f9e 100644 --- a/src/chromium/crTarget.ts +++ b/src/chromium/crTarget.ts @@ -31,7 +31,7 @@ export class CRTarget { private readonly _browser: CRBrowser; private readonly _browserContext: CRBrowserContext; readonly _targetId: string; - private _sessionFactory: () => Promise; + readonly sessionFactory: () => Promise; private _pagePromise: Promise | null = null; _crPage: CRPage | null = null; private _workerPromise: Promise | 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 { 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 { - return this._sessionFactory(); - } - _targetInfoChanged(targetInfo: Protocol.Target.TargetInfo) { this._targetInfo = targetInfo; diff --git a/src/chromium/events.ts b/src/chromium/events.ts index a4b7d8ddceccf..2c85651d87d39 100644 --- a/src/chromium/events.ts +++ b/src/chromium/events.ts @@ -17,8 +17,7 @@ export const Events = { CRBrowserContext: { - TargetCreated: 'targetcreated', - TargetDestroyed: 'targetdestroyed', - TargetChanged: 'targetchanged', + BackgroundPage: 'backgroundpage', + ServiceWorker: 'serviceworker', } }; diff --git a/src/events.ts b/src/events.ts index 4eb800175ff90..c9686e5ad544c 100644 --- a/src/events.ts +++ b/src/events.ts @@ -21,7 +21,8 @@ export const Events = { }, BrowserContext: { - Close: 'close' + Close: 'close', + PageEvent: 'page', }, BrowserServer: { diff --git a/src/page.ts b/src/page.ts index 0abc858e5f4b5..afce4164932f3 100644 --- a/src/page.ts +++ b/src/page.ts @@ -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 { + return this._page; + } +} + export class Page extends platform.EventEmitter { private _closed = false; private _closedCallback: () => void; diff --git a/src/server/chromium.ts b/src/server/chromium.ts index 584dcc3c08fe3..46693e518bca4 100644 --- a/src/server/chromium.ts +++ b/src/server/chromium.ts @@ -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(); diff --git a/test/chromium/chromium.spec.js b/test/chromium/chromium.spec.js index 9f09f4053f8b7..d1ad0d609424f 100644 --- a/test/chromium/chromium.spec.js +++ b/test/chromium/chromium.spec.js @@ -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); - }); - 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 + '/'); - - 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); + expect(serviceWorkerCreated).not.toBeTruthy(); }); - 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'); + 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'); - 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(); diff --git a/test/chromium/headful.spec.js b/test/chromium/headful.spec.js index f9ad8f3462f48..80e22e3877114 100644 --- a/test/chromium/headful.spec.js +++ b/test/chromium/headful.spec.js @@ -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(); }); diff --git a/test/chromium/launcher.spec.js b/test/chromium/launcher.spec.js index feccb79aeb6bc..eea2821e0c339 100644 --- a/test/chromium/launcher.spec.js +++ b/test/chromium/launcher.spec.js @@ -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); diff --git a/test/chromium/oopif.spec.js b/test/chromium/oopif.spec.js index f6504b0ed5ea1..09cc3fe41acb1 100644 --- a/test/chromium/oopif.spec.js +++ b/test/chromium/oopif.spec.js @@ -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'); -} +}; \ No newline at end of file diff --git a/test/chromium/session.spec.js b/test/chromium/session.spec.js index cbce80a4e2660..d51224bb8c469 100644 --- a/test/chromium/session.spec.js +++ b/test/chromium/session.spec.js @@ -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(); + }); + }); };