Skip to content

Commit 618b50c

Browse files
pavelfeldmanaslushnikov
authored andcommitted
api(video): restore the missing video path accessor (microsoft#4132)
1 parent 8f6cef8 commit 618b50c

File tree

13 files changed

+120
-16
lines changed

13 files changed

+120
-16
lines changed

docs/api.md

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
- [class: ConsoleMessage](#class-consolemessage)
1515
- [class: Dialog](#class-dialog)
1616
- [class: Download](#class-download)
17+
- [class: Video](#class-video)
1718
- [class: FileChooser](#class-filechooser)
1819
- [class: Keyboard](#class-keyboard)
1920
- [class: Mouse](#class-mouse)
@@ -787,6 +788,7 @@ page.removeListener('request', logRequest);
787788
- [page.uncheck(selector, [options])](#pageuncheckselector-options)
788789
- [page.unroute(url[, handler])](#pageunrouteurl-handler)
789790
- [page.url()](#pageurl)
791+
- [page.video()](#pagevideo)
790792
- [page.viewportSize()](#pageviewportsize)
791793
- [page.waitForEvent(event[, optionsOrPredicate])](#pagewaitforeventevent-optionsorpredicate)
792794
- [page.waitForFunction(pageFunction[, arg, options])](#pagewaitforfunctionpagefunction-arg-options)
@@ -1900,6 +1902,11 @@ Removes a route created with [page.route(url, handler)](#pagerouteurl-handler).
19001902

19011903
This is a shortcut for [page.mainFrame().url()](#frameurl)
19021904

1905+
#### page.video()
1906+
- returns: <[null]|[Video]>
1907+
1908+
Video object associated with this page.
1909+
19031910
#### page.viewportSize()
19041911
- returns: <[null]|[Object]>
19051912
- `width` <[number]> page width in pixels.
@@ -3429,6 +3436,24 @@ Returns suggested filename for this download. It is typically computed by the br
34293436
Returns downloaded url.
34303437

34313438

3439+
### class: Video
3440+
3441+
When browser context is created with the `videosPath` option, each page has a video object associated with it.
3442+
3443+
```js
3444+
console.log(await page.video().path());
3445+
```
3446+
3447+
<!-- GEN:toc -->
3448+
- [video.path()](#videopath)
3449+
<!-- GEN:stop -->
3450+
3451+
#### video.path()
3452+
- returns: <[string]>
3453+
3454+
Returns the file system path this video will be recorded to. The video is guaranteed to be written to the filesystem upon closing the browser context.
3455+
3456+
34323457
### class: FileChooser
34333458

34343459
[FileChooser] objects are dispatched by the page in the ['filechooser'](#event-filechooser) event.
@@ -4818,6 +4843,7 @@ const { chromium } = require('playwright');
48184843
[URL]: https://nodejs.org/api/url.html
48194844
[USKeyboardLayout]: ../src/usKeyboardLayout.ts "USKeyboardLayout"
48204845
[UnixTime]: https://en.wikipedia.org/wiki/Unix_time "Unix Time"
4846+
[Video]: #class-video "Video"
48214847
[WebKitBrowser]: #class-webkitbrowser "WebKitBrowser"
48224848
[WebSocket]: #class-websocket "WebSocket"
48234849
[Worker]: #class-worker "Worker"

src/browserServerImpl.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -181,7 +181,11 @@ class ConnectedBrowser extends BrowserDispatcher {
181181
const readable = fs.createReadStream(video._path);
182182
await new Promise(f => readable.on('readable', f));
183183
const stream = new StreamDispatcher(this._remoteBrowser!._scope, readable);
184-
this._remoteBrowser!._dispatchEvent('video', { stream, context: contextDispatcher });
184+
this._remoteBrowser!._dispatchEvent('video', {
185+
stream,
186+
context: contextDispatcher,
187+
relativePath: video._relativePath
188+
});
185189
await new Promise<void>(resolve => {
186190
readable.on('close', resolve);
187191
readable.on('end', resolve);

src/client/api.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ export { JSHandle } from './jsHandle';
3232
export { Request, Response, Route } from './network';
3333
export { Page } from './page';
3434
export { Selectors } from './selectors';
35+
export { Video } from './video';
3536
export { Worker } from './worker';
3637

3738
export { ChromiumBrowser } from './chromiumBrowser';

src/client/browser.ts

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -58,8 +58,7 @@ export class Browser extends ChannelOwner<channels.BrowserChannel, channels.Brow
5858
extraHTTPHeaders: options.extraHTTPHeaders ? headersObjectToArray(options.extraHTTPHeaders) : undefined,
5959
};
6060
const context = BrowserContext.from((await this._channel.newContext(contextOptions)).context);
61-
if (this._isRemote)
62-
context._videosPathForRemote = options.videosPath;
61+
context._options = contextOptions;
6362
this._contexts.add(context);
6463
context._logger = logger || this._logger;
6564
return context;

src/client/browserContext.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ export class BrowserContext extends ChannelOwner<channels.BrowserContextChannel,
3737
_timeoutSettings = new TimeoutSettings();
3838
_ownerPage: Page | undefined;
3939
private _closedPromise: Promise<void>;
40-
_videosPathForRemote?: string;
40+
_options: channels.BrowserNewContextParams = {};
4141

4242
static from(context: channels.BrowserContextChannel): BrowserContext {
4343
return (context as any)._object;

src/client/browserType.ts

Lines changed: 5 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ import { TimeoutSettings } from '../utils/timeoutSettings';
2929
import { ChildProcess } from 'child_process';
3030
import { envObjectToArray } from './clientHelper';
3131
import { validateHeaders } from './network';
32-
import { assert, makeWaitForNextTask, headersObjectToArray, createGuid, mkdirIfNeeded } from '../utils/utils';
32+
import { assert, makeWaitForNextTask, headersObjectToArray, mkdirIfNeeded } from '../utils/utils';
3333
import { SelectorsOwner, sharedSelectors } from './selectors';
3434
import { kBrowserClosedError } from '../utils/errors';
3535
import { Stream } from './stream';
@@ -108,6 +108,7 @@ export class BrowserType extends ChannelOwner<channels.BrowserTypeChannel, chann
108108
};
109109
const result = await this._channel.launchPersistentContext(persistentOptions);
110110
const context = BrowserContext.from(result.context);
111+
context._options = persistentOptions;
111112
context._logger = logger;
112113
return context;
113114
}, logger);
@@ -188,16 +189,11 @@ export class BrowserType extends ChannelOwner<channels.BrowserTypeChannel, chann
188189
export class RemoteBrowser extends ChannelOwner<channels.RemoteBrowserChannel, channels.RemoteBrowserInitializer> {
189190
constructor(parent: ChannelOwner, type: string, guid: string, initializer: channels.RemoteBrowserInitializer) {
190191
super(parent, type, guid, initializer);
191-
this._channel.on('video', ({ context, stream }) => this._onVideo(BrowserContext.from(context), Stream.from(stream)));
192+
this._channel.on('video', ({ context, stream, relativePath }) => this._onVideo(BrowserContext.from(context), Stream.from(stream), relativePath));
192193
}
193194

194-
private async _onVideo(context: BrowserContext, stream: Stream) {
195-
if (!context._videosPathForRemote) {
196-
stream._channel.close().catch(e => null);
197-
return;
198-
}
199-
200-
const videoFile = path.join(context._videosPathForRemote, createGuid() + '.webm');
195+
private async _onVideo(context: BrowserContext, stream: Stream, relativePath: string) {
196+
const videoFile = path.join(context._options.videosPath!, relativePath);
201197
await mkdirIfNeeded(videoFile);
202198
stream.stream().pipe(fs.createWriteStream(videoFile));
203199
}

src/client/page.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ import { Size, URLMatch, Headers, LifecycleEvent, WaitForEventOptions, SelectOpt
4444
import { evaluationScript, urlMatches } from './clientHelper';
4545
import { isString, isRegExp, isObject, mkdirIfNeeded, headersObjectToArray } from '../utils/utils';
4646
import { isSafeCloseError } from '../utils/errors';
47+
import { Video } from './video';
4748

4849
const fsWriteFileAsync = util.promisify(fs.writeFile.bind(fs));
4950
const mkdirAsync = util.promisify(fs.mkdir);
@@ -82,6 +83,7 @@ export class Page extends ChannelOwner<channels.PageChannel, channels.PageInitia
8283
readonly _bindings = new Map<string, FunctionWithSource>();
8384
readonly _timeoutSettings: TimeoutSettings;
8485
_isPageCall = false;
86+
private _video: Video | null = null;
8587

8688
static from(page: channels.PageChannel): Page {
8789
return (page as any)._object;
@@ -125,6 +127,7 @@ export class Page extends ChannelOwner<channels.PageChannel, channels.PageInitia
125127
this._channel.on('requestFinished', ({ request }) => this.emit(Events.Page.RequestFinished, Request.from(request)));
126128
this._channel.on('response', ({ response }) => this.emit(Events.Page.Response, Response.from(response)));
127129
this._channel.on('route', ({ route, request }) => this._onRoute(Route.from(route), Request.from(request)));
130+
this._channel.on('video', ({ relativePath }) => this.video()!._setRelativePath(relativePath));
128131
this._channel.on('worker', ({ worker }) => this._onWorker(Worker.from(worker)));
129132

130133
if (this._browserContext._browserName === 'chromium') {
@@ -226,6 +229,15 @@ export class Page extends ChannelOwner<channels.PageChannel, channels.PageInitia
226229
this._channel.setDefaultTimeoutNoReply({ timeout });
227230
}
228231

232+
video(): Video | null {
233+
if (this._video)
234+
return this._video;
235+
if (!this._browserContext._options.videosPath)
236+
return null;
237+
this._video = new Video(this);
238+
return this._video;
239+
}
240+
229241
private _attributeToPage<T>(func: () => T): T {
230242
try {
231243
this._isPageCall = true;

src/client/video.ts

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
/**
2+
* Copyright (c) Microsoft Corporation.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
import * as path from 'path';
18+
import { Page } from './page';
19+
20+
export class Video {
21+
private _page: Page;
22+
private _pathCallback: ((path: string) => void) | undefined;
23+
private _pathPromise: Promise<string>;
24+
25+
constructor(page: Page) {
26+
this._page = page;
27+
this._pathPromise = new Promise(f => this._pathCallback = f);
28+
}
29+
30+
_setRelativePath(relativePath: string) {
31+
this._pathCallback!(path.join(this._page.context()._options.videosPath!, relativePath));
32+
}
33+
34+
path(): Promise<string> {
35+
return this._pathPromise;
36+
}
37+
}

src/dispatchers/pageDispatcher.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414
* limitations under the License.
1515
*/
1616

17-
import { BrowserContext, runAction } from '../server/browserContext';
17+
import { BrowserContext, runAction, Video } from '../server/browserContext';
1818
import { Frame } from '../server/frames';
1919
import { Request } from '../server/network';
2020
import { Page, Worker } from '../server/page';
@@ -66,6 +66,7 @@ export class PageDispatcher extends Dispatcher<Page, channels.PageInitializer> i
6666
}));
6767
page.on(Page.Events.RequestFinished, request => this._dispatchEvent('requestFinished', { request: RequestDispatcher.from(scope, request) }));
6868
page.on(Page.Events.Response, response => this._dispatchEvent('response', { response: new ResponseDispatcher(this._scope, response) }));
69+
page.on(Page.Events.VideoStarted, (video: Video) => this._dispatchEvent('video', { relativePath: video._relativePath }));
6970
page.on(Page.Events.Worker, worker => this._dispatchEvent('worker', { worker: new WorkerDispatcher(this._scope, worker) }));
7071
}
7172

src/protocol/channels.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -125,6 +125,7 @@ export interface RemoteBrowserChannel extends Channel {
125125
export type RemoteBrowserVideoEvent = {
126126
context: BrowserContextChannel,
127127
stream: StreamChannel,
128+
relativePath: string,
128129
};
129130

130131
// ----------- Selectors -----------
@@ -683,6 +684,7 @@ export interface PageChannel extends Channel {
683684
on(event: 'requestFinished', callback: (params: PageRequestFinishedEvent) => void): this;
684685
on(event: 'response', callback: (params: PageResponseEvent) => void): this;
685686
on(event: 'route', callback: (params: PageRouteEvent) => void): this;
687+
on(event: 'video', callback: (params: PageVideoEvent) => void): this;
686688
on(event: 'worker', callback: (params: PageWorkerEvent) => void): this;
687689
setDefaultNavigationTimeoutNoReply(params: PageSetDefaultNavigationTimeoutNoReplyParams, metadata?: Metadata): Promise<PageSetDefaultNavigationTimeoutNoReplyResult>;
688690
setDefaultTimeoutNoReply(params: PageSetDefaultTimeoutNoReplyParams, metadata?: Metadata): Promise<PageSetDefaultTimeoutNoReplyResult>;
@@ -765,6 +767,9 @@ export type PageRouteEvent = {
765767
route: RouteChannel,
766768
request: RequestChannel,
767769
};
770+
export type PageVideoEvent = {
771+
relativePath: string,
772+
};
768773
export type PageWorkerEvent = {
769774
worker: WorkerChannel,
770775
};

0 commit comments

Comments
 (0)