browsertime 17.0.0 → 17.1.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.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,13 @@
1
1
  # Browsertime changelog (we do [semantic versioning](https://semver.org))
2
2
 
3
+ ## 17.1.0 - 2022-02-24
4
+
5
+ ### Added
6
+ * Fix visual-complete progress calculations to not use fuzz/slop, thank you [Gregory Mierzwinski](https://github.com/gmierz) for PR [#1902](https://github.com/sitespeedio/browsertime/pull/1902).
7
+ * Expose chrome-remote-interface client to scripting. This makes it so much easier to run whatever CDP command. Get the raw version `commands.cdp.getRawClient();`. See PR [#1905](https://github.com/sitespeedio/browsertime/pull/1905) for a concrete example until we update the documentation.
8
+ ### Fixed
9
+ * Update to new Chrome HAR [#1906](https://github.com/sitespeedio/browsertime/pull/1906).
10
+ * Fix flushing DNS on Ubuntu 22.04 [#1907](https://github.com/sitespeedio/browsertime/pull/1907).
3
11
  ## 17.0.0 - 2022-02-10
4
12
 
5
13
  ### Breaking changes
@@ -372,7 +372,7 @@ def video_to_frames(
372
372
  viewport_retries,
373
373
  viewport_min_height,
374
374
  viewport_min_width,
375
- full_resolution
375
+ full_resolution,
376
376
  ):
377
377
  """Extract the video frames"""
378
378
  global client_viewport
@@ -413,9 +413,7 @@ def video_to_frames(
413
413
  if orange_file is not None:
414
414
  remove_frames_before_orange(dir, orange_file)
415
415
  remove_orange_frames(dir, orange_file)
416
- find_render_start(
417
- dir, orange_file, cropped, is_mobile
418
- )
416
+ find_render_start(dir, orange_file, cropped, is_mobile)
419
417
  adjust_frame_times(dir)
420
418
  eliminate_duplicate_frames(dir, cropped, is_mobile)
421
419
  crop_viewport(dir)
@@ -508,6 +506,7 @@ def find_recording_platform(video):
508
506
 
509
507
  return is_mobile
510
508
 
509
+
511
510
  def remove_frames_before_orange(directory, orange_file):
512
511
  """Remove stray frames from the start of the video"""
513
512
  frames = sorted(glob.glob(os.path.join(directory, "video-*.png")))
@@ -533,6 +532,7 @@ def remove_frames_before_orange(directory, orange_file):
533
532
  logging.debug("Removing pre-orange frame %s", frame)
534
533
  os.remove(frame)
535
534
 
535
+
536
536
  def remove_orange_frames(directory, orange_file):
537
537
  """Remove orange frames from the beginning of the video"""
538
538
  frames = sorted(glob.glob(os.path.join(directory, "video-*.png")))
@@ -708,6 +708,7 @@ def find_video_viewport(
708
708
 
709
709
  return viewport, cropped
710
710
 
711
+
711
712
  def adjust_frame_times(directory):
712
713
  offset = None
713
714
  frames = sorted(glob.glob(os.path.join(directory, "video-*.png")))
@@ -728,6 +729,7 @@ def adjust_frame_times(directory):
728
729
  dest = os.path.join(directory, "ms_{0:06d}.png".format(new_time))
729
730
  os.rename(frame, dest)
730
731
 
732
+
731
733
  def find_render_start(directory, orange_file, cropped, is_mobile):
732
734
  logging.debug("Finding Render Start...")
733
735
  try:
@@ -902,6 +904,7 @@ def eliminate_duplicate_frames(directory, cropped, is_mobile):
902
904
  except BaseException:
903
905
  logging.exception("Error processing frames for duplicates")
904
906
 
907
+
905
908
  def crop_viewport(directory):
906
909
  if client_viewport is not None:
907
910
  try:
@@ -928,7 +931,9 @@ def crop_viewport(directory):
928
931
  def get_decimate_filter():
929
932
  decimate = None
930
933
  try:
931
- filters = subprocess.check_output(["ffmpeg", "-filters"], stderr=subprocess.STDOUT, encoding="UTF-8")
934
+ filters = subprocess.check_output(
935
+ ["ffmpeg", "-filters"], stderr=subprocess.STDOUT, encoding="UTF-8"
936
+ )
932
937
  lines = filters.split("\n")
933
938
  match = re.compile(
934
939
  r"(?P<filter>[\w]*decimate).*V->V.*Remove near-duplicate frames"
@@ -1005,6 +1010,7 @@ def is_color_frame(file, color_file):
1005
1010
  frame_cache[file][color_file] = bool(match)
1006
1011
  return match
1007
1012
 
1013
+
1008
1014
  def colors_are_similar(a, b, threshold=15):
1009
1015
  similar = True
1010
1016
  sum = 0
@@ -1073,6 +1079,7 @@ def generate_orange_png(orange_file):
1073
1079
  except BaseException:
1074
1080
  logging.exception("Error generating orange png " + orange_file)
1075
1081
 
1082
+
1076
1083
  ##########################################################################
1077
1084
  # Histogram calculations
1078
1085
  ##########################################################################
@@ -1190,6 +1197,7 @@ def convert_to_jpeg(directory, quality):
1190
1197
 
1191
1198
  logging.debug("Done converting video frames to JPEG")
1192
1199
 
1200
+
1193
1201
  ##########################################################################
1194
1202
  # Visual Metrics
1195
1203
  ##########################################################################
@@ -1266,9 +1274,7 @@ def calculate_visual_metrics(
1266
1274
  viewport = hero_data["viewport"]
1267
1275
  hero_timings = []
1268
1276
  for hero in hero_data["heroes"]:
1269
- hero_time = calculate_hero_time(
1270
- progress, dirs, hero, viewport
1271
- )
1277
+ hero_time = calculate_hero_time(progress, dirs, hero, viewport)
1272
1278
  if hero_time is not None:
1273
1279
  hero_timings.append(
1274
1280
  {
@@ -1352,30 +1358,34 @@ def calculate_visual_progress(histograms):
1352
1358
 
1353
1359
 
1354
1360
  def calculate_frame_progress(histogram, start, final):
1361
+ """Calculate the progress percentage of a given frame histogram.
1362
+
1363
+ This method finds the visually-complete progress by taking a sum of
1364
+ all the differences in the histogram between the current frame, and the
1365
+ final frame. The initial/first frame is used to remove values that are
1366
+ consitent between the first, and final frame (i.e. it's a baseline).
1367
+
1368
+ Note that this method should not be using a slop/fuzz because we aren't
1369
+ looking at individual pixel intensities which is where the fuzz can be
1370
+ found. Within individual channels, we hit an issue where we cannot tell
1371
+ if a value in the red channel is purely from a red colour in the image, or
1372
+ if it's mixed with other colours such as grey (e.g. red with rbg(240,0,0),
1373
+ and light grey with rgb(245,245,245)).
1374
+ """
1355
1375
  total = 0
1356
1376
  matched = 0
1357
- slop = 5 # allow for matching slight color variations
1358
- channels = ["r", "g", "b"]
1359
- for channel in channels:
1360
- channel_total = 0
1361
- channel_matched = 0
1362
- buckets = 256
1363
- available = [0 for i in range(buckets)]
1364
- for i in range(buckets):
1365
- available[i] = abs(histogram[channel][i] - start[channel][i])
1366
- for i in range(buckets):
1367
- target = abs(final[channel][i] - start[channel][i])
1368
- if target:
1369
- channel_total += target
1370
- low = max(0, i - slop)
1371
- high = min(buckets, i + slop)
1372
- for j in range(low, high):
1373
- this_match = min(target, available[j])
1374
- available[j] -= this_match
1375
- channel_matched += this_match
1376
- target -= this_match
1377
- total += channel_total
1378
- matched += channel_matched
1377
+ for channel in ("r", "g", "b"):
1378
+ for pixel_ind in range(256):
1379
+ curr = histogram[channel][pixel_ind]
1380
+ init = start[channel][pixel_ind]
1381
+ target = final[channel][pixel_ind]
1382
+
1383
+ curr_diff = abs(curr - init)
1384
+ target_diff = abs(target - init)
1385
+
1386
+ matched += min(curr_diff, target_diff)
1387
+ total += target_diff
1388
+
1379
1389
  progress = (float(matched) / float(total)) if total else 1
1380
1390
  return math.floor(progress * 100)
1381
1391
 
@@ -1609,16 +1619,14 @@ def calculate_hero_time(progress, directory, hero, viewport):
1609
1619
  def check_config():
1610
1620
  ok = True
1611
1621
 
1612
-
1613
1622
  if get_decimate_filter() is not None:
1614
- logging.debug('FFMPEG found')
1623
+ logging.debug("FFMPEG found")
1615
1624
  else:
1616
1625
  print("ffmpeg: FAIL")
1617
1626
  ok = False
1618
1627
 
1619
-
1620
1628
  if sys.version_info >= (3, 6):
1621
- logging.debug('Python 3.6+ found')
1629
+ logging.debug("Python 3.6+ found")
1622
1630
  else:
1623
1631
  print("Python 3.6+: FAIL")
1624
1632
  ok = False
@@ -1626,33 +1634,31 @@ def check_config():
1626
1634
  try:
1627
1635
  import numpy as np
1628
1636
 
1629
- logging.debug('Numpy found')
1637
+ logging.debug("Numpy found")
1630
1638
  except BaseException:
1631
1639
  print("Numpy: FAIL")
1632
1640
  ok = False
1633
1641
 
1634
-
1635
1642
  try:
1636
1643
  import cv2
1637
-
1638
- logging.debug('OpenCV-Python found')
1644
+
1645
+ logging.debug("OpenCV-Python found")
1639
1646
  except BaseException:
1640
1647
  print("OpenCV-Python: FAIL")
1641
1648
  ok = False
1642
1649
 
1643
-
1644
1650
  try:
1645
1651
  from PIL import Image, ImageCms, ImageDraw, ImageOps # noqa
1646
1652
 
1647
- logging.debug('Pillow found')
1653
+ logging.debug("Pillow found")
1648
1654
  except BaseException:
1649
1655
  print("Pillow: FAIL")
1650
1656
  ok = False
1651
1657
 
1652
-
1653
1658
  try:
1654
1659
  from ssim import compute_ssim # noqa
1655
- logging.debug('SSIM found')
1660
+
1661
+ logging.debug("SSIM found")
1656
1662
  except BaseException:
1657
1663
  print("SSIM: FAIL")
1658
1664
  ok = False
@@ -1662,8 +1668,10 @@ def check_config():
1662
1668
 
1663
1669
  def check_process(command, output):
1664
1670
  ok = False
1665
- try:
1666
- out = subprocess.check_output(command, stderr=subprocess.STDOUT, shell=True, encoding="UTF-8")
1671
+ try:
1672
+ out = subprocess.check_output(
1673
+ command, stderr=subprocess.STDOUT, shell=True, encoding="UTF-8"
1674
+ )
1667
1675
  if out.find(output) > -1:
1668
1676
  ok = True
1669
1677
  except BaseException:
@@ -1834,11 +1842,7 @@ def main():
1834
1842
 
1835
1843
  options = parser.parse_args()
1836
1844
 
1837
- if (
1838
- not options.check
1839
- and not options.dir
1840
- and not options.video
1841
- ):
1845
+ if not options.check and not options.dir and not options.video:
1842
1846
  parser.error(
1843
1847
  "A video, Directory of images or histograms file needs to be provided.\n\n"
1844
1848
  "Use -h to see available options"
@@ -1910,7 +1914,7 @@ def main():
1910
1914
  options.viewportminwidth,
1911
1915
  options.full,
1912
1916
  )
1913
-
1917
+
1914
1918
  # Calculate the histograms and visual metrics
1915
1919
  calculate_histograms(directory, histogram_file, options.force)
1916
1920
  metrics = calculate_visual_metrics(
@@ -157,6 +157,10 @@ export class ChromeDevtoolsProtocol {
157
157
  });
158
158
  }
159
159
 
160
+ getRawClient() {
161
+ return this.cdpClient;
162
+ }
163
+
160
164
  async setRequestHeaders(requestHeaders) {
161
165
  // Our cli don't validate parameters
162
166
  // so -run will become -r etc
@@ -51,6 +51,14 @@ export class ChromeDevelopmentToolsProtocol {
51
51
  }
52
52
  }
53
53
 
54
+ getRawClient() {
55
+ if (this.browserName === 'chrome' || this.browserName === 'edge') {
56
+ return this.engineDelegate.getCDPClient().getRawClient();
57
+ } else {
58
+ throw new Error('DevToolsProtocol only supported in Chrome and Edge');
59
+ }
60
+ }
61
+
54
62
  async send(command, arguments_) {
55
63
  if (this.browserName === 'chrome' || this.browserName === 'edge') {
56
64
  try {
@@ -25,6 +25,11 @@ export async function flushDNS(options) {
25
25
  });
26
26
  } else if (process.platform === 'linux') {
27
27
  log.info('Flush DNS cache on Linux');
28
+
29
+ // Unbuntu 22.04
30
+ await execa('sudo', ['resolvectl', 'flush-caches'], {
31
+ reject: false
32
+ });
28
33
  await execa('sudo', ['systemd-resolve', '--flush-caches'], {
29
34
  reject: false
30
35
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "description": "Browsertime",
3
- "version": "17.0.0",
3
+ "version": "17.1.0",
4
4
  "bin": "./bin/browsertime.js",
5
5
  "type": "module",
6
6
  "dependencies": {
@@ -12,7 +12,7 @@
12
12
  "@sitespeed.io/throttle": "5.0.0",
13
13
  "@sitespeed.io/tracium": "0.3.3",
14
14
  "btoa": "1.2.1",
15
- "chrome-har": "0.13.0",
15
+ "chrome-har": "0.13.1",
16
16
  "chrome-remote-interface": "0.32.0",
17
17
  "dayjs": "1.11.7",
18
18
  "execa": "6.1.0",