All files / src recorder.js

100% Statements 27/27
75% Branches 3/4
100% Functions 5/5
100% Lines 26/26

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119                                              6x                                               5x     5x 5x 11x                 5x 5x   5x     5x 11x 2x               5x 5x     5x 11x 11x     7x         5x   5x                           1x   1x 1x   1x 1x 1x     1x  
'use strict';
/**
 * Represents a single run of a Fresnel scenario in a browser.
 *
 * Interacts with: {@link external:puppeteer/Page puppeteer/Page},
 * {@link Probe}.
 *
 * @private
 * @module recorder
 */
 
/**
 * Replace curly-brace placeholders with values from the given object.
 *
 *     expandString( 'Hello {PLANET}', { PLANET: 'World' } );
 *     // > "Hello World"
 *
 * @ignore
 * @param {string} str
 * @param {Object} vars
 * @return {string}
 */
function expandString( str, vars ) {
	return str.replace( /{([A-Z_]+)}/g, ( _, key ) => vars[ key ] );
}
 
/**
 * Run the scenario in the given browser and record data from the probes.
 *
 * A scenario consists of running the following steps:
 *
 * - Create a new browser tab.
 * - Set the viewport.
 * - Run "before" callbacks of probes.
 * - Load the url.
 * - Run "after" callback of probes.
 *
 * @param {Object} options Scenario options
 * @param {string} options.url
 * @param {Object} options.viewport
 * @param {Probe[]|Set} probes List of probes
 * @param {Writer} writer File writer for this scenario
 * @param {external:puppeteer/Browser} browser
 * @param {Function} progress
 * @return {Object} Probe data objects.
 */
async function run( options, probes, writer, browser, progress ) {
	const url = expandString( options.url, process.env );
 
	// This will store the data collected by probes, keyed by probe name.
	const datas = {};
	for ( const probe of probes ) {
		datas[ probe.name ] = {};
	}
 
	// Each run should be similar to the first (no http cache or local storage shared
	// between runs). Simply creating and closing tabs within the same browser process
	// for each run would violate that. Instead, create a new (temporary) profile for
	// each run, and create the tab within that. Puppeteer refers to temporary profiles
	// as "incognito contexts", but.. this doesn't actually involve "Incognito mode".
 
	const context = await browser.createIncognitoBrowserContext();
	const page = await context.newPage();
 
	await page.setViewport( options.viewport );
 
	// Run 'setup' callbacks
	for ( const probe of probes ) {
		if ( probe.before ) {
			await probe.before(
				page,
				writer.prefix( `${probe.name}--` )
			);
		}
	}
 
	// Load the url
	progress( 'recorder/navigate', url );
	await page.goto( url );
 
	// Run 'collect' callbacks
	for ( const probe of probes ) {
		Eif ( probe.after ) {
			await probe.after(
				page,
				writer.prefix( `${probe.name}--` ),
				( data ) => { Object.assign( datas[ probe.name ], data ); }
			);
		}
	}
 
	await context.close();
 
	return datas;
}
 
/**
 * Warm up a given scenario. (Probes are active during warmups.)
 *
 * Called from {@link module:conductor~record conductor}.
 *
 * @param {Object} options Scenario options
 * @param {string} options.url
 * @param {Object} options.viewport
 * @param {external:puppeteer/Browser} browser
 */
async function warmup( options, browser ) {
	const url = expandString( options.url, process.env );
 
	const context = await browser.createIncognitoBrowserContext();
	const page = await context.newPage();
 
	await page.setViewport( options.viewport );
	await page.goto( url );
	await context.close();
}
 
module.exports = { run, warmup };