browsertime 14.8.1 → 14.10.2

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,6 +1,23 @@
1
1
  # Browsertime changelog (we do [semantic versioning](https://semver.org))
2
2
 
3
3
 
4
+ ## 14.10.2 - 2021-11-20
5
+ ### Fixed
6
+ * Disabled the version check for Edge/Edgedriver in Edgedriver [#1678](https://github.com/sitespeedio/browsertime/pull/1678).
7
+
8
+ ## 14.10.1 - 2021-11-19
9
+ ### Fixed
10
+ * Disabled the automatic Chrome/Chromedriver version check in Chromedriver [#1676](https://github.com/sitespeedio/browsertime/pull/1676).
11
+ * Loop until we find a frame with a good viewport or until we run out of retries for Visual Metrics. Thank you [Gregory Mierzwinski](https://github.com/gmierz) for PR [#1668](https://github.com/sitespeedio/browsertime/pull/1668).
12
+
13
+ ## 14.10.0 - 2021-11-16
14
+ ### Added
15
+ * Updated to Chromedriver 96 and Chrome 96 in the Docker container [#1670](https://github.com/sitespeedio/browsertime/pull/1670).
16
+
17
+ ### Fixed
18
+ * Added checks for Firefox HAR that it actually has a page.
19
+ * Updated day js.
20
+
4
21
  ## 14.9.0 - 2021-11-07
5
22
  ### Added
6
23
  * Updated to new Chome HAR PR [#1666](https://github.com/sitespeedio/browsertime/pull/1666) that inlcudes chunk information.
@@ -44,12 +44,12 @@ import shutil
44
44
  import subprocess
45
45
  import tempfile
46
46
 
47
- if (sys.version_info > (3, 0)):
48
- GZIP_TEXT = 'wt'
49
- GZIP_READ_TEXT = 'rt'
47
+ if sys.version_info > (3, 0):
48
+ GZIP_TEXT = "wt"
49
+ GZIP_READ_TEXT = "rt"
50
50
  else:
51
- GZIP_TEXT = 'w'
52
- GZIP_READ_TEXT = 'r'
51
+ GZIP_TEXT = "w"
52
+ GZIP_READ_TEXT = "r"
53
53
 
54
54
  # Globals
55
55
  options = None
@@ -72,6 +72,9 @@ def video_to_frames(
72
72
  multiple,
73
73
  find_viewport,
74
74
  viewport_time,
75
+ viewport_retries,
76
+ viewport_min_height,
77
+ viewport_min_width,
75
78
  full_resolution,
76
79
  timeline_file,
77
80
  trim_end,
@@ -93,7 +96,13 @@ def video_to_frames(
93
96
  if os.path.isdir(directory):
94
97
  directory = os.path.realpath(directory)
95
98
  viewport = find_video_viewport(
96
- video, directory, find_viewport, viewport_time
99
+ video,
100
+ directory,
101
+ find_viewport,
102
+ viewport_time,
103
+ viewport_retries,
104
+ viewport_min_height,
105
+ viewport_min_width,
97
106
  )
98
107
  gc.collect()
99
108
  if extract_frames(video, directory, full_resolution, viewport):
@@ -169,10 +178,10 @@ def extract_frames(video, directory, full_resolution, viewport):
169
178
  ]
170
179
  logging.debug(" ".join(command))
171
180
  lines = []
172
- if (sys.version_info > (3, 0)):
173
- proc = subprocess.Popen(command, stderr=subprocess.PIPE, encoding='UTF-8')
181
+ if sys.version_info > (3, 0):
182
+ proc = subprocess.Popen(command, stderr=subprocess.PIPE, encoding="UTF-8")
174
183
  else:
175
- proc = subprocess.Popen(command, stderr=subprocess.PIPE)
184
+ proc = subprocess.Popen(command, stderr=subprocess.PIPE)
176
185
  while proc.poll() is None:
177
186
  lines.extend(iter(proc.stderr.readline, ""))
178
187
 
@@ -354,65 +363,107 @@ def find_image_viewport(file):
354
363
  return viewport
355
364
 
356
365
 
357
- def find_video_viewport(video, directory, find_viewport, viewport_time):
366
+ def find_video_viewport(
367
+ video,
368
+ directory,
369
+ find_viewport,
370
+ viewport_time,
371
+ viewport_retries,
372
+ viewport_min_height,
373
+ viewport_min_width,
374
+ ):
358
375
  logging.debug("Finding Video Viewport...")
359
376
  viewport = None
360
377
  try:
361
378
  from PIL import Image
362
379
 
363
- frame = os.path.join(directory, "viewport.png")
364
- if os.path.isfile(frame):
365
- os.remove(frame)
366
- command = ["ffmpeg", "-i", video]
367
- if viewport_time:
368
- command.extend(["-ss", viewport_time])
369
- command.extend(["-frames:v", "1", frame])
370
- subprocess.check_output(command)
371
- if os.path.isfile(frame):
372
- with Image.open(frame) as im:
373
- width, height = im.size
374
- logging.debug("%s is %dx%d", frame, width, height)
375
- if options.notification:
376
- im = Image.open(frame)
377
- pixels = im.load()
378
- middle = int(math.floor(height / 2))
379
- # Find the top edge (at ~40% in to deal with browsers that
380
- # color the notification area)
381
- x = int(width * 0.4)
382
- y = 0
383
- background = pixels[x, y]
384
- top = None
385
- while top is None and y < middle:
386
- if not colors_are_similar(background, pixels[x, y]):
387
- top = y
388
- else:
389
- y += 1
390
- if top is None:
391
- top = 0
392
- logging.debug("Window top edge is {0:d}".format(top))
393
-
394
- # Find the bottom edge
395
- x = 0
396
- y = height - 1
397
- bottom = None
398
- while bottom is None and y > middle:
399
- if not colors_are_similar(background, pixels[x, y]):
400
- bottom = y
401
- else:
402
- y -= 1
403
- if bottom is None:
404
- bottom = height - 1
405
- logging.debug("Window bottom edge is {0:d}".format(bottom))
380
+ retries = -1
381
+
382
+ while (
383
+ viewport is None
384
+ or viewport["height"] <= viewport_min_height
385
+ or viewport["width"] <= viewport_min_width
386
+ ):
387
+ retries += 1
388
+ if retries >= 1:
389
+ # In some cases, the first frame is not an orange screen or a screen
390
+ # with a solid color. In this case, we need to try finding the viewport
391
+ # using the next frame. The `viewport_retries` dictates the maximum number
392
+ # of frames to check.
393
+ if retries >= viewport_retries:
394
+ logging.exception(
395
+ "Could not calculate a viewport after %s tries.",
396
+ viewport_retries,
397
+ )
398
+ break
399
+ logging.info("Failed to find a good viewport. Retrying...")
406
400
 
407
- viewport = {"x": 0, "y": top, "width": width, "height": (bottom - top)}
401
+ logging.debug("Using frame " + str(retries))
408
402
 
409
- elif find_viewport:
410
- viewport = find_image_viewport(frame)
411
- else:
412
- viewport = {"x": 0, "y": 0, "width": width, "height": height}
413
- os.remove(frame)
403
+ frame = os.path.join(directory, "viewport.png")
404
+ if os.path.isfile(frame):
405
+ os.remove(frame)
414
406
 
415
- except Exception:
407
+ command = ["ffmpeg", "-i", video]
408
+ if viewport_time:
409
+ command.extend(["-ss", viewport_time])
410
+
411
+ # Pull one frame from the video starting with the frame at
412
+ # the `retries` index
413
+ command.extend(["-vf", "select=gte(n\\,%s)" % retries])
414
+ command.extend(["-frames:v", "1", frame])
415
+ subprocess.check_output(command)
416
+
417
+ if os.path.isfile(frame):
418
+ with Image.open(frame) as im:
419
+ width, height = im.size
420
+ logging.debug("%s is %dx%d", frame, width, height)
421
+ if options.notification:
422
+ im = Image.open(frame)
423
+ pixels = im.load()
424
+ middle = int(math.floor(height / 2))
425
+ # Find the top edge (at ~40% in to deal with browsers that
426
+ # color the notification area)
427
+ x = int(width * 0.4)
428
+ y = 0
429
+ background = pixels[x, y]
430
+ top = None
431
+ while top is None and y < middle:
432
+ if not colors_are_similar(background, pixels[x, y]):
433
+ top = y
434
+ else:
435
+ y += 1
436
+ if top is None:
437
+ top = 0
438
+ logging.debug("Window top edge is {0:d}".format(top))
439
+
440
+ # Find the bottom edge
441
+ x = 0
442
+ y = height - 1
443
+ bottom = None
444
+ while bottom is None and y > middle:
445
+ if not colors_are_similar(background, pixels[x, y]):
446
+ bottom = y
447
+ else:
448
+ y -= 1
449
+ if bottom is None:
450
+ bottom = height - 1
451
+ logging.debug("Window bottom edge is {0:d}".format(bottom))
452
+
453
+ viewport = {
454
+ "x": 0,
455
+ "y": top,
456
+ "width": width,
457
+ "height": (bottom - top),
458
+ }
459
+
460
+ elif find_viewport:
461
+ viewport = find_image_viewport(frame)
462
+ else:
463
+ viewport = {"x": 0, "y": 0, "width": width, "height": height}
464
+ os.remove(frame)
465
+
466
+ except Exception as e:
416
467
  viewport = None
417
468
 
418
469
  return viewport
@@ -787,10 +838,14 @@ def crop_viewport(directory):
787
838
  def get_decimate_filter():
788
839
  decimate = None
789
840
  try:
790
- if (sys.version_info > (3, 0)):
791
- filters = subprocess.check_output(['ffmpeg', '-filters'], stderr=subprocess.STDOUT, encoding='UTF-8')
841
+ if sys.version_info > (3, 0):
842
+ filters = subprocess.check_output(
843
+ ["ffmpeg", "-filters"], stderr=subprocess.STDOUT, encoding="UTF-8"
844
+ )
792
845
  else:
793
- filters = subprocess.check_output(['ffmpeg', '-filters'], stderr=subprocess.STDOUT)
846
+ filters = subprocess.check_output(
847
+ ["ffmpeg", "-filters"], stderr=subprocess.STDOUT
848
+ )
794
849
  lines = filters.split("\n")
795
850
  match = re.compile(
796
851
  r"(?P<filter>[\w]*decimate).*V->V.*Remove near-duplicate frames"
@@ -864,10 +919,14 @@ def is_color_frame(file, color_file):
864
919
  image_magick["compare"],
865
920
  )
866
921
 
867
- if (sys.version_info > (3, 0)):
868
- compare = subprocess.Popen(command, stderr=subprocess.PIPE, shell=True, encoding='UTF-8')
922
+ if sys.version_info > (3, 0):
923
+ compare = subprocess.Popen(
924
+ command, stderr=subprocess.PIPE, shell=True, encoding="UTF-8"
925
+ )
869
926
  else:
870
- compare = subprocess.Popen(command, stderr=subprocess.PIPE, shell=True)
927
+ compare = subprocess.Popen(
928
+ command, stderr=subprocess.PIPE, shell=True
929
+ )
871
930
  out, err = compare.communicate()
872
931
  if re.match("^[0-9]+$", err):
873
932
  different_pixels = int(err)
@@ -908,9 +967,11 @@ def is_white_frame(file, white_file):
908
967
  ).format(
909
968
  image_magick["convert"], white_file, file, crop, image_magick["compare"]
910
969
  )
911
- if (sys.version_info > (3, 0)):
912
- compare = subprocess.Popen(command, stderr=subprocess.PIPE, shell=True, encoding='UTF-8')
913
- else:
970
+ if sys.version_info > (3, 0):
971
+ compare = subprocess.Popen(
972
+ command, stderr=subprocess.PIPE, shell=True, encoding="UTF-8"
973
+ )
974
+ else:
914
975
  compare = subprocess.Popen(command, stderr=subprocess.PIPE, shell=True)
915
976
  out, err = compare.communicate()
916
977
  if re.match("^[0-9]+$", err):
@@ -966,9 +1027,11 @@ def frames_match(image1, image2, fuzz_percent, max_differences, crop_region, mas
966
1027
  )
967
1028
  if platform.system() != "Windows":
968
1029
  command = command.replace("(", "\\(").replace(")", "\\)")
969
- if (sys.version_info > (3, 0)):
970
- compare = subprocess.Popen(command, stderr=subprocess.PIPE, shell=True, encoding='UTF-8')
971
- else:
1030
+ if sys.version_info > (3, 0):
1031
+ compare = subprocess.Popen(
1032
+ command, stderr=subprocess.PIPE, shell=True, encoding="UTF-8"
1033
+ )
1034
+ else:
972
1035
  compare = subprocess.Popen(command, stderr=subprocess.PIPE, shell=True)
973
1036
  out, err = compare.communicate()
974
1037
  if re.match("^[0-9]+$", err):
@@ -1686,7 +1749,7 @@ def calculate_contentful_speed_index(progress, directory):
1686
1749
  command = "{0} {1} -canny 2x2+8%+8% -define histogram:unique-colors=true -format %c histogram:info:-".format(
1687
1750
  image_magick["convert"], current_frame
1688
1751
  )
1689
- output = subprocess.check_output(command, shell=True).decode('utf-8')
1752
+ output = subprocess.check_output(command, shell=True).decode("utf-8")
1690
1753
  logging.debug("Output %s" % output)
1691
1754
 
1692
1755
  # Take the last matching pixel count, which is the pixel count from the last
@@ -1887,28 +1950,28 @@ def calculate_hero_time(progress, directory, hero, viewport):
1887
1950
  def check_config():
1888
1951
  ok = True
1889
1952
 
1890
- print("ffmpeg: ",)
1953
+ print("ffmpeg: ")
1891
1954
  if get_decimate_filter() is not None:
1892
1955
  print("OK")
1893
1956
  else:
1894
1957
  print("FAIL")
1895
1958
  ok = False
1896
1959
 
1897
- print("convert: ",)
1960
+ print("convert: ")
1898
1961
  if check_process("{0} -version".format(image_magick["convert"]), "ImageMagick"):
1899
1962
  print("OK")
1900
1963
  else:
1901
1964
  print("FAIL")
1902
1965
  ok = False
1903
1966
 
1904
- print("compare: ",)
1967
+ print("compare: ")
1905
1968
  if check_process("{0} -version".format(image_magick["compare"]), "ImageMagick"):
1906
1969
  print("OK")
1907
1970
  else:
1908
1971
  print("FAIL")
1909
1972
  ok = False
1910
1973
 
1911
- print("Pillow: ",)
1974
+ print("Pillow: ")
1912
1975
  try:
1913
1976
  from PIL import Image, ImageDraw # noqa
1914
1977
 
@@ -1917,7 +1980,7 @@ def check_config():
1917
1980
  print("FAIL")
1918
1981
  ok = False
1919
1982
 
1920
- print("SSIM: ",)
1983
+ print("SSIM: ")
1921
1984
  try:
1922
1985
  from ssim import compute_ssim # noqa
1923
1986
 
@@ -1932,8 +1995,10 @@ def check_config():
1932
1995
  def check_process(command, output):
1933
1996
  ok = False
1934
1997
  try:
1935
- if (sys.version_info > (3, 0)):
1936
- out = subprocess.check_output(command, stderr=subprocess.STDOUT, shell=True, encoding='UTF-8')
1998
+ if sys.version_info > (3, 0):
1999
+ out = subprocess.check_output(
2000
+ command, stderr=subprocess.STDOUT, shell=True, encoding="UTF-8"
2001
+ )
1937
2002
  else:
1938
2003
  out = subprocess.check_output(command, stderr=subprocess.STDOUT, shell=True)
1939
2004
  if out.find(output) > -1:
@@ -1976,7 +2041,7 @@ def main():
1976
2041
  "--logfile", help="Write log messages to given file instead of stdout"
1977
2042
  )
1978
2043
  parser.add_argument(
1979
- '--logformat',
2044
+ "--logformat",
1980
2045
  help="Formatting for the log messages",
1981
2046
  default="%(asctime)s.%(msecs)03d - %(message)s",
1982
2047
  )
@@ -2081,6 +2146,28 @@ def main():
2081
2146
  help="Time of the video frame to use for identifying the viewport "
2082
2147
  "(in HH:MM:SS.xx format).",
2083
2148
  )
2149
+ parser.add_argument(
2150
+ "--viewportretries",
2151
+ type=int,
2152
+ default=5,
2153
+ help="Number of times to attempt to obtain a viewport. Analagous to the "
2154
+ "number of frames to try to find a viewport with. By default, up to the "
2155
+ "first 5 frames are used.",
2156
+ )
2157
+ parser.add_argument(
2158
+ "--viewportminheight",
2159
+ type=int,
2160
+ default=0,
2161
+ help="The minimum possible height (in pixels) for the viewport. Used when "
2162
+ "attempting to find the viewport size. Defaults to 0.",
2163
+ )
2164
+ parser.add_argument(
2165
+ "--viewportminwidth",
2166
+ type=int,
2167
+ default=0,
2168
+ help="The minimum possible width (in pixels) for the viewport. Used when "
2169
+ "attempting to find the viewport size. Defaults to 0.",
2170
+ )
2084
2171
  parser.add_argument(
2085
2172
  "-s",
2086
2173
  "--start",
@@ -2213,9 +2300,7 @@ def main():
2213
2300
  )
2214
2301
  else:
2215
2302
  logging.basicConfig(
2216
- level=log_level,
2217
- format=options.logformat,
2218
- datefmt="%H:%M:%S",
2303
+ level=log_level, format=options.logformat, datefmt="%H:%M:%S"
2219
2304
  )
2220
2305
 
2221
2306
  if options.multiple:
@@ -2285,6 +2370,9 @@ def main():
2285
2370
  options.multiple,
2286
2371
  options.viewport,
2287
2372
  options.viewporttime,
2373
+ options.viewportretries,
2374
+ options.viewportminheight,
2375
+ options.viewportminwidth,
2288
2376
  options.full,
2289
2377
  options.timeline,
2290
2378
  options.trimend,
@@ -25,6 +25,10 @@ module.exports.configureBuilder = function (builder, baseDir, options) {
25
25
  }
26
26
 
27
27
  const serviceBuilder = new chrome.ServiceBuilder(chromedriverPath);
28
+
29
+ // Remove the check that matches the Chromedriver version with Chrome version.
30
+ serviceBuilder.addArguments('--disable-build-check');
31
+
28
32
  if (options.chrome && options.chrome.chromedriverPort) {
29
33
  serviceBuilder.setPort(options.chrome.chromedriverPort);
30
34
  }
@@ -38,7 +42,6 @@ module.exports.configureBuilder = function (builder, baseDir, options) {
38
42
  serviceBuilder.enableVerboseLogging();
39
43
  }
40
44
  chrome.setDefaultService(serviceBuilder.build());
41
-
42
45
  hasConfiguredChromeDriverService = true;
43
46
  }
44
47
 
@@ -25,6 +25,10 @@ module.exports.configureBuilder = function (builder, baseDir, options) {
25
25
  }
26
26
 
27
27
  const serviceBuilder = new edge.ServiceBuilder(edgedriverPath);
28
+
29
+ // Remove the check that matches the Edgedriver version with Edge version.
30
+ serviceBuilder.addArguments('--disable-build-check');
31
+
28
32
  if (
29
33
  options.verbose >= 2 ||
30
34
  chromeConfig.enableChromeDriverLog ||
@@ -10,8 +10,12 @@ module.exports = async function (runner, includeResponseBodies) {
10
10
  async function triggerExport() {
11
11
  try {
12
12
  const result = await HAR.triggerExport();
13
- result.pages[0].title = document.URL;
14
- return callback({'har': {log: result}});
13
+ if (result.pages.length > 0) {
14
+ result.pages[0].title = document.URL;
15
+ return callback({'har': {log: result}});
16
+ } else {
17
+ return callback({'error': 'The HAR export trigger returned an empty HAR' + JSON.stringify(result)});
18
+ }
15
19
  }
16
20
  catch(e) {
17
21
  // Sometimes the HAR trigger is really slow so then HAR is not defined.
package/package.json CHANGED
@@ -1,15 +1,15 @@
1
1
  {
2
2
  "description": "Browsertime",
3
- "version": "14.8.1",
3
+ "version": "14.10.2",
4
4
  "bin": "./bin/browsertime.js",
5
5
  "dependencies": {
6
6
  "@sitespeed.io/throttle": "3.0.0",
7
7
  "@devicefarmer/adbkit": "2.11.3",
8
8
  "btoa": "1.2.1",
9
- "@sitespeed.io/chromedriver": "95.0.4638-17",
9
+ "@sitespeed.io/chromedriver": "96.0.4664-35",
10
10
  "chrome-har": "0.12.0",
11
11
  "chrome-remote-interface": "0.31.0",
12
- "dayjs": "1.10.5",
12
+ "dayjs": "1.10.7",
13
13
  "execa": "5.1.1",
14
14
  "fast-stats": "0.0.6",
15
15
  "find-up": "5.0.0",