browsertime 16.16.0 → 17.0.0-beta.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (121) hide show
  1. package/CHANGELOG.md +52 -0
  2. package/bin/browsertime.js +43 -43
  3. package/bin/browsertimeWebPageReplay.js +10 -9
  4. package/browserscripts/pageinfo/generator.js +1 -1
  5. package/index.js +14 -13
  6. package/lib/android/gnirehtet.js +14 -15
  7. package/lib/android/index.js +92 -89
  8. package/lib/android/root.js +6 -9
  9. package/lib/chrome/chromeDevtoolsProtocol.js +46 -45
  10. package/lib/chrome/har.js +12 -13
  11. package/lib/chrome/longTaskMetrics.js +3 -5
  12. package/lib/chrome/parseCpuTrace.js +13 -10
  13. package/lib/chrome/settings/chromeAndroidOptions.js +1 -3
  14. package/lib/chrome/settings/chromeDesktopOptions.js +1 -3
  15. package/lib/chrome/settings/traceCategories.js +1 -3
  16. package/lib/chrome/traceCategoriesParser.js +35 -36
  17. package/lib/chrome/webdriver/builder.js +18 -26
  18. package/lib/chrome/webdriver/chromium.js +56 -46
  19. package/lib/chrome/webdriver/setupChromiumOptions.js +32 -27
  20. package/lib/connectivity/humble.js +13 -14
  21. package/lib/connectivity/index.js +74 -77
  22. package/lib/connectivity/trafficShapeParser.js +31 -35
  23. package/lib/core/engine/collector.js +25 -22
  24. package/lib/core/engine/command/addText.js +24 -27
  25. package/lib/core/engine/command/android.js +7 -11
  26. package/lib/core/engine/command/cache.js +3 -4
  27. package/lib/core/engine/command/chromeDevToolsProtocol.js +20 -20
  28. package/lib/core/engine/command/click.js +48 -49
  29. package/lib/core/engine/command/debug.js +3 -4
  30. package/lib/core/engine/command/javaScript.js +18 -19
  31. package/lib/core/engine/command/measure.js +31 -37
  32. package/lib/core/engine/command/meta.js +1 -4
  33. package/lib/core/engine/command/mouse/clickAndHold.js +36 -39
  34. package/lib/core/engine/command/mouse/contextClick.js +17 -18
  35. package/lib/core/engine/command/mouse/doubleClick.js +17 -19
  36. package/lib/core/engine/command/mouse/index.js +5 -9
  37. package/lib/core/engine/command/mouse/mouseMove.js +19 -23
  38. package/lib/core/engine/command/mouse/singleClick.js +15 -19
  39. package/lib/core/engine/command/navigation.js +12 -14
  40. package/lib/core/engine/command/screenshot.js +1 -4
  41. package/lib/core/engine/command/scroll.js +4 -7
  42. package/lib/core/engine/command/select.js +26 -26
  43. package/lib/core/engine/command/set.js +25 -23
  44. package/lib/core/engine/command/stopWatch.js +5 -8
  45. package/lib/core/engine/command/switch.js +18 -20
  46. package/lib/core/engine/command/util/lcpHighlightScript.js +1 -2
  47. package/lib/core/engine/command/util/lsHighlightScript.js +1 -2
  48. package/lib/core/engine/command/wait.js +18 -18
  49. package/lib/core/engine/index.js +81 -71
  50. package/lib/core/engine/iteration.js +93 -79
  51. package/lib/core/engine/run.js +5 -9
  52. package/lib/core/pageCompleteChecks/defaultPageCompleteCheck.js +1 -2
  53. package/lib/core/pageCompleteChecks/pageCompleteCheckByInactivity.js +1 -3
  54. package/lib/core/pageCompleteChecks/spaInactivity.js +1 -3
  55. package/lib/core/seleniumRunner.js +92 -100
  56. package/lib/core/webdriver/index.js +24 -21
  57. package/lib/edge/webdriver/builder.js +21 -26
  58. package/lib/extensionserver/httpServer.js +17 -21
  59. package/lib/extensionserver/index.js +8 -12
  60. package/lib/extensionserver/setup.js +12 -12
  61. package/lib/firefox/geckoProfiler.js +27 -21
  62. package/lib/firefox/getHAR.js +7 -8
  63. package/lib/firefox/memoryReport.js +9 -11
  64. package/lib/firefox/perfStats.js +5 -7
  65. package/lib/firefox/settings/disableSafeBrowsingPreferences.js +1 -3
  66. package/lib/firefox/settings/disableTrackingProtectionPreferences.js +1 -3
  67. package/lib/firefox/settings/firefoxPreferences.js +2 -4
  68. package/lib/firefox/settings/geckoProfilerDefaults.js +5 -9
  69. package/lib/firefox/webdriver/builder.js +66 -63
  70. package/lib/firefox/webdriver/firefox.js +43 -48
  71. package/lib/safari/webdriver/builder.js +4 -5
  72. package/lib/safari/webdriver/safari.js +4 -8
  73. package/lib/screenshot/defaults.js +1 -3
  74. package/lib/screenshot/index.js +24 -21
  75. package/lib/support/browserScript.js +65 -56
  76. package/lib/support/cli.js +123 -85
  77. package/lib/support/dns.js +6 -7
  78. package/lib/support/engineUtils.js +85 -38
  79. package/lib/support/errors.js +4 -13
  80. package/lib/support/fileUtil.js +81 -68
  81. package/lib/support/filters.js +15 -20
  82. package/lib/support/getViewPort.js +6 -7
  83. package/lib/support/har/index.js +208 -216
  84. package/lib/support/images/index.js +54 -26
  85. package/lib/support/logging.js +20 -17
  86. package/lib/support/pathToFolder.js +18 -23
  87. package/lib/support/preURL.js +5 -6
  88. package/lib/support/processes.js +4 -6
  89. package/lib/support/setResourceTimingBufferSize.js +2 -4
  90. package/lib/support/statistics.js +58 -56
  91. package/lib/support/stop.js +7 -8
  92. package/lib/support/storageManager.js +44 -44
  93. package/lib/support/tcpdump.js +22 -16
  94. package/lib/support/userTiming.js +7 -13
  95. package/lib/support/util.js +204 -224
  96. package/lib/support/xvfb.js +11 -13
  97. package/lib/video/defaults.js +6 -10
  98. package/lib/video/postprocessing/finetune/addTextToVideo.js +15 -15
  99. package/lib/video/postprocessing/finetune/convertFps.js +14 -8
  100. package/lib/video/postprocessing/finetune/getFont.js +7 -9
  101. package/lib/video/postprocessing/finetune/getTimingMetrics.js +19 -18
  102. package/lib/video/postprocessing/finetune/index.js +40 -36
  103. package/lib/video/postprocessing/finetune/removeOrange.js +10 -11
  104. package/lib/video/postprocessing/visualmetrics/extraMetrics.js +17 -13
  105. package/lib/video/postprocessing/visualmetrics/getVideoMetrics.js +12 -13
  106. package/lib/video/postprocessing/visualmetrics/visualMetrics.js +117 -128
  107. package/lib/video/screenRecording/android/recorder.js +12 -17
  108. package/lib/video/screenRecording/desktop/convert.js +13 -11
  109. package/lib/video/screenRecording/desktop/desktopRecorder.js +29 -24
  110. package/lib/video/screenRecording/desktop/ffmpegRecorder.js +60 -63
  111. package/lib/video/screenRecording/desktop/osx/getSPDisplaysDataType.js +4 -6
  112. package/lib/video/screenRecording/desktop/osx/getScreen.js +9 -10
  113. package/lib/video/screenRecording/firefox/firefoxWindowRecorder.js +54 -54
  114. package/lib/video/screenRecording/ios/convertToMp4.js +10 -11
  115. package/lib/video/screenRecording/ios/iosRecorder.js +23 -21
  116. package/lib/video/screenRecording/iosSimulator/convertToMp4.js +10 -11
  117. package/lib/video/screenRecording/iosSimulator/recorder.js +10 -9
  118. package/lib/video/screenRecording/recorder.js +8 -9
  119. package/lib/video/screenRecording/setOrangeBackground.js +5 -6
  120. package/lib/video/video.js +28 -34
  121. package/package.json +19 -17
@@ -1,24 +1,29 @@
1
- 'use strict';
1
+ import { promisify } from 'node:util';
2
+ import { unlink as _unlink, rename as _rename } from 'node:fs';
3
+ import intel from 'intel';
4
+ import get from 'lodash.get';
5
+ import { start as _start, stop as _stop } from './ffmpegRecorder.js';
6
+ import convert from './convert.js';
7
+ import {
8
+ xvfbDisplay,
9
+ framerate as _framerate,
10
+ crf as _crf,
11
+ convert as _convert,
12
+ threads as _threads
13
+ } from '../../defaults.js';
14
+ import getViewPort from '../../../support/getViewPort.js';
15
+ const unlink = promisify(_unlink);
16
+ const rename = promisify(_rename);
17
+ const log = intel.getLogger('browsertime.video');
2
18
 
3
- const log = require('intel').getLogger('browsertime.video');
4
- const get = require('lodash.get');
5
- const util = require('util');
6
- const fs = require('fs');
7
- const ffmpegRecorder = require('./ffmpegRecorder');
8
- const convert = require('./convert');
9
- const defaults = require('../../defaults');
10
- const getViewPort = require('../../../support/getViewPort');
11
- const unlink = util.promisify(fs.unlink);
12
- const rename = util.promisify(fs.rename);
13
-
14
- module.exports = class DesktopRecorder {
19
+ export default class DesktopRecorder {
15
20
  constructor(options) {
16
- this.display = get(options, 'xvfbParams.display', defaults.xvfbDisplay);
17
- this.framerate = get(options, 'videoParams.framerate', defaults.framerate);
21
+ this.display = get(options, 'xvfbParams.display', xvfbDisplay);
22
+ this.framerate = get(options, 'videoParams.framerate', _framerate);
18
23
  this.nice = get(options, 'videoParams.nice', 0);
19
- this.crf = get(options, 'videoParams.crf', defaults.crf);
20
- this.convert = get(options, 'videoParams.convert', defaults.convert);
21
- this.threads = get(options, 'videoParams.threads', defaults.threads);
24
+ this.crf = get(options, 'videoParams.crf', _crf);
25
+ this.convert = get(options, 'videoParams.convert', _convert);
26
+ this.threads = get(options, 'videoParams.threads', _threads);
22
27
  this.viewPort = getViewPort(options);
23
28
  this.origin = '0,0';
24
29
  this.offset = { x: 0, y: 0 };
@@ -28,7 +33,7 @@ module.exports = class DesktopRecorder {
28
33
  async start(file) {
29
34
  this.filePath = file;
30
35
 
31
- this.recording = ffmpegRecorder.start({
36
+ this.recording = _start({
32
37
  display: this.display,
33
38
  size: this.viewPort,
34
39
  filePath: this.filePath,
@@ -45,14 +50,14 @@ module.exports = class DesktopRecorder {
45
50
 
46
51
  async stop(destination) {
47
52
  log.debug('Stop screen recording');
48
- await ffmpegRecorder.stop(this.recording);
53
+ await _stop(this.recording);
49
54
  // FIXME update to rename/move file
50
55
  // The destination file could exixt of we use --resultDir
51
56
  // so make sure we remove it first
52
57
  if (this.options.resultDir) {
53
58
  try {
54
59
  await unlink(destination);
55
- } catch (e) {
60
+ } catch {
56
61
  // Nothing to see here
57
62
  }
58
63
  }
@@ -63,15 +68,15 @@ module.exports = class DesktopRecorder {
63
68
  } else {
64
69
  await rename(this.filePath, destination);
65
70
  }
66
- } catch (e) {
71
+ } catch (error) {
67
72
  log.error(
68
73
  'Converting the video failed. Converting from ' +
69
74
  this.filePath +
70
75
  ' to ' +
71
76
  destination
72
77
  );
73
- throw e;
78
+ throw error;
74
79
  }
75
80
  log.debug(`Writing to ${destination}`);
76
81
  }
77
- };
82
+ }
@@ -1,11 +1,10 @@
1
- 'use strict';
1
+ import { execa } from 'execa';
2
+ import intel from 'intel';
3
+ import getScreenOnOSX from './osx/getScreen.js';
4
+ import getSPDisplaysDataType from './osx/getSPDisplaysDataType.js';
5
+ const log = intel.getLogger('browsertime.video');
2
6
 
3
- const execa = require('execa');
4
- const log = require('intel').getLogger('browsertime.video');
5
- const getScreenOnOSX = require('./osx/getScreen');
6
- const getSPDisplaysDataType = require('./osx/getSPDisplaysDataType');
7
-
8
- async function buildX11FfmpegArgs({
7
+ async function buildX11FfmpegArguments({
9
8
  display,
10
9
  screen = 0,
11
10
  framerate = 30,
@@ -23,7 +22,7 @@ async function buildX11FfmpegArgs({
23
22
  const uiregex = /(?<=UI Looks like:\s*)(\d+\s*x\s*\d+)/gm;
24
23
  const uiLooksLike = SPDisplaysDataType.match(uiregex);
25
24
  const widthAndHeight = size.split('x');
26
- let devicePixelRatio = SPDisplaysDataType.indexOf('Retina') > -1 ? 2 : 1;
25
+ let devicePixelRatio = SPDisplaysDataType.includes('Retina') ? 2 : 1;
27
26
  // Lets add better check in the future. Right now: If we have uilookslike and
28
27
  // is not the same as the resolution, say it DPI 2
29
28
  if (uiLooksLike && resolution[0].trim() !== uiLooksLike[0].trim()) {
@@ -85,12 +84,12 @@ async function buildX11FfmpegArgs({
85
84
  }
86
85
  }
87
86
 
88
- async function startRecording(ffmpegArgs, nice, filePath) {
87
+ async function startRecording(ffmpegArguments, nice, filePath) {
89
88
  async function waitForRecording(readableStream) {
90
89
  return new Promise((resolve, reject) => {
91
90
  readableStream.on('data', data => {
92
91
  log.trace(data.toString());
93
- if (data.toString().match(/Press \[q] to stop/)) {
92
+ if (/Press \[q] to stop/.test(data.toString())) {
94
93
  // readableStream.removeAllListeners('data');
95
94
  return resolve();
96
95
  }
@@ -100,78 +99,76 @@ async function startRecording(ffmpegArgs, nice, filePath) {
100
99
  }
101
100
  let ffmpegProcess;
102
101
  if (nice !== 0) {
103
- ffmpegArgs.unshift('ffmpeg');
104
- ffmpegArgs.unshift(`${nice}`);
105
- ffmpegArgs.unshift('-n');
106
- log.debug('Start FFMPEG with %j', ffmpegArgs);
107
- ffmpegProcess = execa('nice', ffmpegArgs);
102
+ ffmpegArguments.unshift('ffmpeg');
103
+ ffmpegArguments.unshift(`${nice}`);
104
+ ffmpegArguments.unshift('-n');
105
+ log.debug('Start FFMPEG with %j', ffmpegArguments);
106
+ ffmpegProcess = execa('nice', ffmpegArguments);
108
107
  } else {
109
- log.debug('Start FFMPEG with %j', ffmpegArgs);
110
- ffmpegProcess = execa('ffmpeg', ffmpegArgs);
108
+ log.debug('Start FFMPEG with %j', ffmpegArguments);
109
+ ffmpegProcess = execa('ffmpeg', ffmpegArguments);
111
110
  }
112
111
 
113
112
  // Race to catch if ffmpeg exists early, or if recording has started
114
113
  return Promise.race([ffmpegProcess, waitForRecording(ffmpegProcess.stderr)])
115
114
  .then(() => {
116
- log.debug('Started ffmpeg with ' + ffmpegArgs.join(' '));
115
+ log.debug('Started ffmpeg with ' + ffmpegArguments.join(' '));
117
116
 
118
117
  return {
119
118
  filePath,
120
119
  ffmpegProcess
121
120
  };
122
121
  })
123
- .catch(e => {
124
- log.error(e);
125
- throw e;
122
+ .catch(error => {
123
+ log.error(error);
124
+ throw error;
126
125
  });
127
126
  }
128
127
 
129
- module.exports = {
130
- /**
131
- * @returns A promise for a recording object. Pass it to stopRecording.
132
- */
133
- async start({
128
+ export /**
129
+ * @returns A promise for a recording object. Pass it to stopRecording.
130
+ */
131
+ async function start({
132
+ display,
133
+ origin,
134
+ size,
135
+ filePath,
136
+ offset,
137
+ framerate,
138
+ crf,
139
+ nice,
140
+ threads
141
+ }) {
142
+ const widthAndHeight = size.split('x');
143
+ const withoutTopBar =
144
+ Number.parseInt(widthAndHeight[0]) -
145
+ offset.x +
146
+ 'x' +
147
+ (Number.parseInt(widthAndHeight[1]) - offset.y);
148
+
149
+ const ffmpegArguments = await buildX11FfmpegArguments({
134
150
  display,
135
151
  origin,
136
- size,
152
+ size: withoutTopBar,
137
153
  filePath,
138
- offset,
139
154
  framerate,
140
155
  crf,
141
- nice,
156
+ offset,
142
157
  threads
143
- }) {
144
- const widthAndHeight = size.split('x');
145
- const withoutTopBar =
146
- parseInt(widthAndHeight[0]) -
147
- offset.x +
148
- 'x' +
149
- (parseInt(widthAndHeight[1]) - offset.y);
150
-
151
- const ffmpegArgs = await buildX11FfmpegArgs({
152
- display,
153
- origin,
154
- size: withoutTopBar,
155
- filePath,
156
- framerate,
157
- crf,
158
- offset,
159
- threads
160
- });
161
- return startRecording(ffmpegArgs, nice, filePath);
162
- },
163
- /**
164
- * @returns A promise for a recording result, with a filePath property.
165
- */
166
- async stop(recording) {
167
- return Promise.resolve(recording).then(rec => {
168
- const process = rec.ffmpegProcess;
169
- delete rec.ffmpegProcess;
170
- process.stdin.write('q');
171
- return Promise.resolve(process).then(() => {
172
- log.debug('Stopped ffmpeg');
173
- return rec;
174
- });
158
+ });
159
+ return startRecording(ffmpegArguments, nice, filePath);
160
+ }
161
+ export /**
162
+ * @returns A promise for a recording result, with a filePath property.
163
+ */
164
+ async function stop(recording) {
165
+ return Promise.resolve(recording).then(rec => {
166
+ const process = rec.ffmpegProcess;
167
+ delete rec.ffmpegProcess;
168
+ process.stdin.write('q');
169
+ return Promise.resolve(process).then(() => {
170
+ log.debug('Stopped ffmpeg');
171
+ return rec;
175
172
  });
176
- }
177
- };
173
+ });
174
+ }
@@ -1,10 +1,8 @@
1
- 'use strict';
1
+ import { execaCommand as command } from 'execa';
2
2
 
3
- const execa = require('execa');
4
-
5
- module.exports = async function () {
6
- const output = await execa.command('system_profiler SPDisplaysDataType', {
3
+ export default async function () {
4
+ const output = await command('system_profiler SPDisplaysDataType', {
7
5
  shell: true
8
6
  });
9
7
  return output.stdout;
10
- };
8
+ }
@@ -1,10 +1,9 @@
1
- 'use strict';
1
+ import { execa } from 'execa';
2
+ import intel from 'intel';
3
+ const log = intel.getLogger('browsertime.video');
2
4
 
3
- const execa = require('execa');
4
- const log = require('intel').getLogger('browsertime.video');
5
-
6
- module.exports = async function () {
7
- const scriptArgs = [
5
+ export default async function () {
6
+ const scriptArguments = [
8
7
  '-hide_banner',
9
8
  '-f',
10
9
  'avfoundation',
@@ -13,7 +12,7 @@ module.exports = async function () {
13
12
  '-i',
14
13
  0
15
14
  ];
16
- log.debug('Getting screen on OS X using %j', scriptArgs);
15
+ log.debug('Getting screen on OS X using %j', scriptArguments);
17
16
 
18
17
  /*
19
18
  ffmpeg -hide_banner -f avfoundation -list_devices true -i 0
@@ -25,8 +24,8 @@ module.exports = async function () {
25
24
  [AVFoundation input device @ 0x7fb919e00780] [0] Built-in Microphone
26
25
  */
27
26
 
28
- const output = await execa('ffmpeg', scriptArgs, { reject: false });
27
+ const output = await execa('ffmpeg', scriptArguments, { reject: false });
29
28
  log.debug('Output: %s', output.stderr);
30
- const reg = /\[(\d+)\] Capture screen/;
29
+ const reg = /\[(\d+)] Capture screen/;
31
30
  return output.stderr.match(reg)[1];
32
- };
31
+ }
@@ -1,21 +1,22 @@
1
- 'use strict';
2
-
3
- const log = require('intel').getLogger('browsertime.video');
4
- const util = require('util');
5
- const fs = require('fs');
6
- const path = require('path');
7
- const execa = require('execa');
8
- const { removeDirAndFiles } = require('../../../support/fileUtil');
9
- const { Android } = require('../../../android');
10
-
11
- const unlink = util.promisify(fs.unlink);
1
+ import { promisify } from 'node:util';
2
+ import {
3
+ unlink as _unlink,
4
+ readdirSync,
5
+ statSync,
6
+ createWriteStream,
7
+ copyFileSync
8
+ } from 'node:fs';
9
+ import { join } from 'node:path';
10
+ import intel from 'intel';
11
+ import { execa } from 'execa';
12
+ import { removeDirAndFiles } from '../../../support/fileUtil.js';
13
+ import { Android, isAndroidConfigured } from '../../../android/index.js';
14
+ const log = intel.getLogger('browsertime.video');
15
+
16
+ const unlink = promisify(_unlink);
12
17
 
13
18
  async function enableWindowRecorder(enable, browser) {
14
- if (enable) {
15
- enable = 'true';
16
- } else {
17
- enable = 'false';
18
- }
19
+ enable = enable ? 'true' : 'false';
19
20
 
20
21
  // We wrap windowUtils.setCompositionRecording(...) in Promise.resolve() to
21
22
  // work on both the older (synchronous) and newer (asynchronous)
@@ -30,25 +31,24 @@ async function enableWindowRecorder(enable, browser) {
30
31
 
31
32
  return browser.runPrivilegedAsyncScript(
32
33
  script,
33
- 'toggle composition recorder',
34
- undefined
34
+ 'toggle composition recorder'
35
35
  );
36
36
  }
37
37
 
38
38
  function findRecordingDirectory(baseDir) {
39
39
  let closest_mtime = 0;
40
- let directory = undefined;
40
+ let directory;
41
41
 
42
- fs.readdirSync(baseDir).forEach(file => {
42
+ for (const file of readdirSync(baseDir)) {
43
43
  if (file.startsWith('windowrecording-')) {
44
- let fullPath = path.join(baseDir, file);
45
- let mtime = fs.statSync(fullPath).mtime;
44
+ let fullPath = join(baseDir, file);
45
+ let mtime = statSync(fullPath).mtime;
46
46
  if (mtime > closest_mtime) {
47
47
  closest_mtime = mtime;
48
48
  directory = fullPath;
49
49
  }
50
50
  }
51
- });
51
+ }
52
52
 
53
53
  log.debug('Using window recording directory: ' + directory);
54
54
  if (directory === undefined) {
@@ -60,36 +60,36 @@ function findRecordingDirectory(baseDir) {
60
60
  function findRecordingStartTime(recordingDir) {
61
61
  // Recording directory has the following format:
62
62
  // .../browsertime-results/<web address>/<date time>/windowrecording-<unix recording start time>
63
- return recordingDir.substr(recordingDir.lastIndexOf('-') + 1);
63
+ return recordingDir.slice(recordingDir.lastIndexOf('-') + 1);
64
64
  }
65
65
 
66
66
  function findTimeToFirstFrame(recordingDir) {
67
- const files = fs.readdirSync(recordingDir);
68
- if (files === undefined || files.length < 1) {
67
+ const files = readdirSync(recordingDir);
68
+ if (files === undefined || files.length === 0) {
69
69
  log.error('Could not find recordings in ' + recordingDir);
70
70
  } else {
71
71
  // Iterate through files, extract the time from the filenames, and return the smallest time
72
- return files.reduce((acc, fileName) => {
73
- const time = parseInt(fileName.split('-')[2].split('.')[0], 10);
74
- return Math.min(acc, time);
75
- }, Infinity);
72
+ // eslint-disable-next-line unicorn/no-array-reduce
73
+ return files.reduce((accumulator, fileName) => {
74
+ const time = Number.parseInt(fileName.split('-')[2].split('.')[0], 10);
75
+ return Math.min(accumulator, time);
76
+ }, Number.POSITIVE_INFINITY);
76
77
  }
77
78
  }
78
79
 
79
80
  function writeFrameDurationsToFile(directoryName, imageFiles) {
80
81
  return new Promise((resolve, reject) => {
81
- const stream = fs.createWriteStream(
82
- path.join(directoryName, 'durations.txt')
83
- );
82
+ const stream = createWriteStream(join(directoryName, 'durations.txt'));
84
83
  stream.once('open', function () {
85
84
  stream.write(
86
- "file '" + path.join(directoryName, imageFiles[0].filename) + "'\n"
85
+ "file '" + join(directoryName, imageFiles[0].filename) + "'\n"
87
86
  );
88
- for (let i = 1; i < imageFiles.length; i++) {
89
- let duration = (imageFiles[i].offset - imageFiles[i - 1].offset) / 1000;
87
+ for (let index = 1; index < imageFiles.length; index++) {
88
+ let duration =
89
+ (imageFiles[index].offset - imageFiles[index - 1].offset) / 1000;
90
90
  stream.write('duration ' + duration.toString() + '\n');
91
91
  stream.write(
92
- "file '" + path.join(directoryName, imageFiles[i].filename) + "'\n"
92
+ "file '" + join(directoryName, imageFiles[index].filename) + "'\n"
93
93
  );
94
94
  }
95
95
  stream.end();
@@ -97,7 +97,7 @@ function writeFrameDurationsToFile(directoryName, imageFiles) {
97
97
  resolve(true);
98
98
  });
99
99
  stream.on('error', () => {
100
- reject(Error('Error writing durations to file.'));
100
+ reject(new Error('Error writing durations to file.'));
101
101
  });
102
102
  });
103
103
  });
@@ -106,19 +106,19 @@ function writeFrameDurationsToFile(directoryName, imageFiles) {
106
106
  async function generateVideo(destination, recordingDirectoryName) {
107
107
  let imageFiles = [];
108
108
 
109
- fs.readdirSync(recordingDirectoryName).forEach(file => {
109
+ for (const file of readdirSync(recordingDirectoryName)) {
110
110
  // Format of the filenames are "frame-<num>-<offset>.png"
111
111
  // where num is frame number and offset is time since capture start in ms.
112
112
  let fields = file.split('-');
113
113
  let frameno = ('0000' + fields[1]).slice(-4);
114
114
  let newFilename = 'frame' + frameno + '.png';
115
115
  let offset = fields[2].split('.')[0];
116
- fs.copyFileSync(
117
- path.join(recordingDirectoryName, file),
118
- path.join(recordingDirectoryName, newFilename)
116
+ copyFileSync(
117
+ join(recordingDirectoryName, file),
118
+ join(recordingDirectoryName, newFilename)
119
119
  );
120
120
  imageFiles.push({ filename: newFilename, offset: offset });
121
- });
121
+ }
122
122
 
123
123
  imageFiles.sort(function (a, b) {
124
124
  if (a.filename < b.filename) return -1;
@@ -127,13 +127,13 @@ async function generateVideo(destination, recordingDirectoryName) {
127
127
  });
128
128
  await writeFrameDurationsToFile(recordingDirectoryName, imageFiles);
129
129
 
130
- const vfr_args = [
130
+ const vfr_arguments = [
131
131
  '-f',
132
132
  'concat',
133
133
  '-safe',
134
134
  '0',
135
135
  '-i',
136
- path.join(recordingDirectoryName, 'durations.txt'),
136
+ join(recordingDirectoryName, 'durations.txt'),
137
137
  '-vf',
138
138
  'pad=ceil(iw/2)*2:ceil(ih/2)*2',
139
139
  '-vsync',
@@ -142,24 +142,24 @@ async function generateVideo(destination, recordingDirectoryName) {
142
142
  'yuv420p',
143
143
  destination
144
144
  ];
145
- log.debug('Executing command: ffmpeg ' + vfr_args.join(' '));
146
- await execa('ffmpeg', vfr_args);
145
+ log.debug('Executing command: ffmpeg ' + vfr_arguments.join(' '));
146
+ await execa('ffmpeg', vfr_arguments);
147
147
  return removeDirAndFiles(recordingDirectoryName);
148
148
  }
149
149
 
150
- module.exports = class FirefoxWindowRecorder {
150
+ export default class FirefoxWindowRecorder {
151
151
  constructor(options, browser, baseDir) {
152
152
  this.options = options;
153
153
  this.browser = browser;
154
154
  this.baseDir = baseDir;
155
- this.recordingStartTime = null;
156
- this.timeToFirstFrame = null;
155
+ this.recordingStartTime = undefined;
156
+ this.timeToFirstFrame = undefined;
157
157
  }
158
158
 
159
159
  async start() {
160
160
  log.info('Start firefox window recorder.');
161
161
 
162
- if (this.options.android) {
162
+ if (isAndroidConfigured(this.options)) {
163
163
  this.android = new Android(this.options);
164
164
  await this.android.removePathOnSdCard(
165
165
  'browsertime-firefox-windowrecording'
@@ -174,7 +174,7 @@ module.exports = class FirefoxWindowRecorder {
174
174
  log.info('Stop firefox window recorder.');
175
175
  await enableWindowRecorder(false, this.browser);
176
176
 
177
- if (this.options.android) {
177
+ if (isAndroidConfigured(this.options)) {
178
178
  const fullPathOnSdCard = this.android.getFullPathOnSdCard(
179
179
  'browsertime-firefox-windowrecording'
180
180
  );
@@ -187,7 +187,7 @@ module.exports = class FirefoxWindowRecorder {
187
187
  if (this.options.resultDir) {
188
188
  try {
189
189
  await unlink(destination);
190
- } catch (e) {
190
+ } catch {
191
191
  // Nothing to see here
192
192
  }
193
193
  }
@@ -203,4 +203,4 @@ module.exports = class FirefoxWindowRecorder {
203
203
  );
204
204
  log.debug(`Writing to ${destination}`);
205
205
  }
206
- };
206
+ }
@@ -1,23 +1,22 @@
1
- 'use strict';
1
+ import { execa } from 'execa';
2
+ import intel from 'intel';
3
+ const log = intel.getLogger('browsertime.video');
2
4
 
3
- const execa = require('execa');
4
- const log = require('intel').getLogger('browsertime.video');
5
-
6
- module.exports = async function convert(src, dest) {
7
- const scriptArgs = [
5
+ export default async function convert(source, destination) {
6
+ const scriptArguments = [
8
7
  '-framerate',
9
8
  60,
10
9
  '-i',
11
- src,
10
+ source,
12
11
  '-c:v',
13
12
  'copy',
14
13
  '-f',
15
14
  'mp4',
16
- dest,
15
+ destination,
17
16
  '-y'
18
17
  ];
19
18
 
20
- log.debug('Converting video from h264 to mp4 %j', scriptArgs);
19
+ log.debug('Converting video from h264 to mp4 %j', scriptArguments);
21
20
 
22
- return execa('ffmpeg', scriptArgs);
23
- };
21
+ return execa('ffmpeg', scriptArguments);
22
+ }
@@ -1,36 +1,38 @@
1
- const log = require('intel').getLogger('browsertime.video');
2
- const path = require('path');
3
- const execa = require('execa');
4
- const util = require('util');
5
- const fs = require('fs');
6
- const unlink = util.promisify(fs.unlink);
7
- const convert = require('./convertToMp4');
8
- const moduleRootPath = path.resolve(__dirname, '..', '..', '..', '..');
9
- const QVH = path.resolve(moduleRootPath, 'vendor', 'mac', 'x86', 'qvh');
1
+ import { fileURLToPath } from 'node:url';
2
+ import path from 'node:path';
3
+ import { resolve, join } from 'node:path';
4
+ import { execaCommand as command } from 'execa';
5
+ import { promisify } from 'node:util';
6
+ import { unlink as _unlink } from 'node:fs';
7
+ import convert from './convertToMp4.js';
8
+ import intel from 'intel';
9
+ const log = intel.getLogger('browsertime.video');
10
+ const unlink = promisify(_unlink);
11
+ const __dirname = path.dirname(fileURLToPath(import.meta.url));
12
+ const moduleRootPath = resolve(__dirname, '..', '..', '..', '..');
13
+ const QVH = resolve(moduleRootPath, 'vendor', 'mac', 'x86', 'qvh');
10
14
  const delay = ms => new Promise(res => setTimeout(res, ms));
11
15
 
12
- module.exports = class IOSRecorder {
16
+ export default class IOSRecorder {
13
17
  constructor(options, baseDir) {
14
18
  this.options = options;
15
19
  this.uuid = options.safari.deviceUDID;
16
- this.tmpVideo = path.join(baseDir, 'tmp.h264');
17
- this.tmpSound = path.join(baseDir, 'tmp.wav');
20
+ this.tmpVideo = join(baseDir, 'tmp.h264');
21
+ this.tmpSound = join(baseDir, 'tmp.wav');
18
22
  }
19
23
 
20
24
  static async activate() {
21
25
  log.debug('Activate configuration for IOS recorder.');
22
- return execa
23
- .command(
24
- QVH + ' activate ' + (this.uuid ? '--udid=' + this.uuid + ' ' : ''),
25
- { shell: true }
26
- )
27
- .stdout.pipe(process.stdout);
26
+ return command(
27
+ QVH + ' activate ' + (this.uuid ? '--udid=' + this.uuid + ' ' : ''),
28
+ { shell: true }
29
+ ).stdout.pipe(process.stdout);
28
30
  }
29
31
 
30
32
  async start() {
31
33
  log.info('Start IOS recorder.');
32
34
 
33
- this.qvhProcessProcess = execa.command(
35
+ this.qvhProcessProcess = command(
34
36
  QVH +
35
37
  ' record ' +
36
38
  (this.uuid ? '--udid=' + this.uuid + ' ' : '') +
@@ -62,6 +64,6 @@ module.exports = class IOSRecorder {
62
64
  await unlink(this.tmpVideo);
63
65
  await unlink(this.tmpSound);
64
66
  // await rename(this.tmpVideo, destination);
65
- return Promise.resolve(this.qvhProcessProcess);
67
+ return this.qvhProcessProcess;
66
68
  }
67
- };
69
+ }