mirror of
https://github.com/microsoft/playwright.git
synced 2025-06-26 21:40:17 +00:00
feat(screencast): add saveAs and createReadableStream (#3879)
This commit is contained in:
parent
e4e3f82337
commit
459d857bc3
@ -14,10 +14,14 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { Readable } from 'stream';
|
||||
import * as channels from '../protocol/channels';
|
||||
import * as fs from 'fs';
|
||||
import { mkdirIfNeeded } from '../utils/utils';
|
||||
import { Browser } from './browser';
|
||||
import { BrowserContext } from './browserContext';
|
||||
import { ChannelOwner } from './channelOwner';
|
||||
import { Stream } from './stream';
|
||||
|
||||
export class Video extends ChannelOwner<channels.VideoChannel, channels.VideoInitializer> {
|
||||
private _browser: Browser | null;
|
||||
@ -36,4 +40,31 @@ export class Video extends ChannelOwner<channels.VideoChannel, channels.VideoIni
|
||||
throw new Error(`Path is not available when using browserType.connect().`);
|
||||
return (await this._channel.path()).value;
|
||||
}
|
||||
|
||||
async saveAs(path: string): Promise<void> {
|
||||
return this._wrapApiCall('video.saveAs', async () => {
|
||||
if (!this._browser || !this._browser._isRemote) {
|
||||
await this._channel.saveAs({ path });
|
||||
return;
|
||||
}
|
||||
|
||||
const stream = await this.createReadStream();
|
||||
if (!stream)
|
||||
throw new Error('Failed to copy video from server');
|
||||
await mkdirIfNeeded(path);
|
||||
await new Promise((resolve, reject) => {
|
||||
stream.pipe(fs.createWriteStream(path))
|
||||
.on('finish' as any, resolve)
|
||||
.on('error' as any, reject);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
async createReadStream(): Promise<Readable | null> {
|
||||
const result = await this._channel.stream();
|
||||
if (!result.stream)
|
||||
return null;
|
||||
const stream = Stream.from(result.stream);
|
||||
return stream.stream();
|
||||
}
|
||||
}
|
||||
|
@ -14,9 +14,13 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import * as fs from 'fs';
|
||||
import * as util from 'util';
|
||||
import * as channels from '../protocol/channels';
|
||||
import { Video } from '../server/browserContext';
|
||||
import { mkdirIfNeeded } from '../utils/utils';
|
||||
import { Dispatcher, DispatcherScope } from './dispatcher';
|
||||
import { StreamDispatcher } from './streamDispatcher';
|
||||
|
||||
export class VideoDispatcher extends Dispatcher<Video, channels.VideoInitializer> implements channels.VideoChannel {
|
||||
constructor(scope: DispatcherScope, screencast: Video) {
|
||||
@ -26,4 +30,18 @@ export class VideoDispatcher extends Dispatcher<Video, channels.VideoInitializer
|
||||
async path(): Promise<channels.VideoPathResult> {
|
||||
return { value: await this._object.path() };
|
||||
}
|
||||
|
||||
async saveAs(params: channels.VideoSaveAsParams): Promise<channels.VideoSaveAsResult> {
|
||||
const fileName = await this._object.path();
|
||||
await mkdirIfNeeded(params.path);
|
||||
await util.promisify(fs.copyFile)(fileName, params.path);
|
||||
}
|
||||
|
||||
async stream(): Promise<channels.VideoStreamResult> {
|
||||
const fileName = await this._object.path();
|
||||
const readable = fs.createReadStream(fileName);
|
||||
await new Promise(f => readable.on('readable', f));
|
||||
return { stream: new StreamDispatcher(this._scope, readable) };
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -2144,12 +2144,26 @@ export type DialogDismissResult = void;
|
||||
export type VideoInitializer = {};
|
||||
export interface VideoChannel extends Channel {
|
||||
path(params?: VideoPathParams, metadata?: Metadata): Promise<VideoPathResult>;
|
||||
saveAs(params: VideoSaveAsParams, metadata?: Metadata): Promise<VideoSaveAsResult>;
|
||||
stream(params?: VideoStreamParams, metadata?: Metadata): Promise<VideoStreamResult>;
|
||||
}
|
||||
export type VideoPathParams = {};
|
||||
export type VideoPathOptions = {};
|
||||
export type VideoPathResult = {
|
||||
value: string,
|
||||
};
|
||||
export type VideoSaveAsParams = {
|
||||
path: string,
|
||||
};
|
||||
export type VideoSaveAsOptions = {
|
||||
|
||||
};
|
||||
export type VideoSaveAsResult = void;
|
||||
export type VideoStreamParams = {};
|
||||
export type VideoStreamOptions = {};
|
||||
export type VideoStreamResult = {
|
||||
stream?: StreamChannel,
|
||||
};
|
||||
|
||||
// ----------- Download -----------
|
||||
export type DownloadInitializer = {
|
||||
|
@ -1808,6 +1808,15 @@ Video:
|
||||
returns:
|
||||
value: string
|
||||
|
||||
# Blocks path until saved to the local |path|.
|
||||
saveAs:
|
||||
parameters:
|
||||
path: string
|
||||
|
||||
stream:
|
||||
returns:
|
||||
stream: Stream?
|
||||
|
||||
|
||||
|
||||
Download:
|
||||
|
@ -814,6 +814,10 @@ export function createScheme(tChannel: (name: string) => Validator): Scheme {
|
||||
});
|
||||
scheme.DialogDismissParams = tOptional(tObject({}));
|
||||
scheme.VideoPathParams = tOptional(tObject({}));
|
||||
scheme.VideoSaveAsParams = tObject({
|
||||
path: tString,
|
||||
});
|
||||
scheme.VideoStreamParams = tOptional(tObject({}));
|
||||
scheme.DownloadPathParams = tOptional(tObject({}));
|
||||
scheme.DownloadSaveAsParams = tObject({
|
||||
path: tString,
|
||||
|
@ -423,4 +423,43 @@ describe('screencast', suite => {
|
||||
expect(await videoPlayer.videoWidth()).toBe(1280);
|
||||
expect(await videoPlayer.videoHeight()).toBe(720);
|
||||
});
|
||||
|
||||
it('should create read stream', async ({browser, server}) => {
|
||||
const context = await browser.newContext({_recordVideos: true});
|
||||
|
||||
const page = await context.newPage();
|
||||
const video = await page.waitForEvent('_videostarted') as any;
|
||||
await page.goto(server.PREFIX + '/grid.html');
|
||||
await new Promise(r => setTimeout(r, 1000));
|
||||
const [stream, path] = await Promise.all([
|
||||
video.createReadStream(),
|
||||
video.path(),
|
||||
// TODO: make it work with dead context!
|
||||
page.close(),
|
||||
]);
|
||||
|
||||
const bufs = [];
|
||||
stream.on('data', data => bufs.push(data));
|
||||
await new Promise(f => stream.on('end', f));
|
||||
const streamedData = Buffer.concat(bufs);
|
||||
expect(fs.readFileSync(path).compare(streamedData)).toBe(0);
|
||||
});
|
||||
|
||||
it('should saveAs', async ({browser, server, tmpDir}) => {
|
||||
const context = await browser.newContext({_recordVideos: true});
|
||||
|
||||
const page = await context.newPage();
|
||||
const video = await page.waitForEvent('_videostarted') as any;
|
||||
await page.goto(server.PREFIX + '/grid.html');
|
||||
await new Promise(r => setTimeout(r, 1000));
|
||||
const saveAsPath = path.join(tmpDir, 'v.webm');
|
||||
const [videoPath] = await Promise.all([
|
||||
video.path(),
|
||||
video.saveAs(saveAsPath),
|
||||
// TODO: make it work with dead context!
|
||||
page.close(),
|
||||
]);
|
||||
|
||||
expect(fs.readFileSync(videoPath).compare(fs.readFileSync(saveAsPath))).toBe(0);
|
||||
});
|
||||
});
|
Loading…
x
Reference in New Issue
Block a user