site_compare.py 6.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177
  1. #!/usr/bin/env python
  2. # Copyright (c) 2011 The Chromium Authors. All rights reserved.
  3. # Use of this source code is governed by a BSD-style license that can be
  4. # found in the LICENSE file.
  5. """SiteCompare component to handle bulk scrapes.
  6. Invokes a list of browsers and sends them to a list of URLs,
  7. saving the rendered results to a specified directory, then
  8. performs comparison operations on the resulting bitmaps and
  9. saves the results
  10. """
  11. from __future__ import print_function
  12. # This line is necessary to work around a QEMU bug
  13. import _imaging
  14. import os # Functions for walking the directory tree
  15. import types # Runtime type-checking
  16. import command_line # command-line parsing
  17. import drivers # Functions for driving keyboard/mouse/windows, OS-specific
  18. import operators # Functions that, given two bitmaps as input, produce
  19. # output depending on the performance of an operation
  20. import scrapers # Functions that know how to capture a render from
  21. # particular browsers
  22. import commands.compare2 # compare one page in two versions of same browser
  23. import commands.maskmaker # generate a mask based on repeated scrapes
  24. import commands.measure # measure length of time a page takes to load
  25. import commands.scrape # scrape a URL or series of URLs to a bitmap
  26. # The timeload command is obsolete (too flaky); it may be reinstated
  27. # later but for now it's been superceded by "measure"
  28. # import commands.timeload # measure length of time a page takes to load
  29. def Scrape(browsers, urls, window_size=(1024, 768),
  30. window_pos=(0, 0), timeout=20, save_path=None, **kwargs):
  31. """Invoke one or more browsers over one or more URLs, scraping renders.
  32. Args:
  33. browsers: browsers to invoke with optional version strings
  34. urls: URLs to visit
  35. window_size: size of the browser window to display
  36. window_pos: location of browser window
  37. timeout: time (in seconds) to wait for page to load
  38. save_path: root of save path, automatically appended with browser and
  39. version
  40. kwargs: miscellaneous keyword args, passed to scraper
  41. Returns:
  42. None
  43. @TODO(jhaas): more parameters, or perhaps an indefinite dictionary
  44. parameter, for things like length of time to wait for timeout, speed
  45. of mouse clicks, etc. Possibly on a per-browser, per-URL, or
  46. per-browser-per-URL basis
  47. """
  48. if type(browsers) in types.StringTypes: browsers = [browsers]
  49. if save_path is None:
  50. # default save path is "scrapes" off the current root
  51. save_path = os.path.join(os.path.split(__file__)[0], "Scrapes")
  52. for browser in browsers:
  53. # Browsers should be tuples of (browser, version)
  54. if type(browser) in types.StringTypes: browser = (browser, None)
  55. scraper = scrapers.GetScraper(browser)
  56. full_path = os.path.join(save_path, browser[0], scraper.version)
  57. drivers.windowing.PreparePath(full_path)
  58. scraper.Scrape(urls, full_path, window_size, window_pos, timeout, kwargs)
  59. def Compare(base, compare, ops, root_path=None, out_path=None):
  60. """Compares a series of scrapes using a series of operators.
  61. Args:
  62. base: (browser, version) tuple of version to consider the baseline
  63. compare: (browser, version) tuple of version to compare to
  64. ops: list of operators plus operator arguments
  65. root_path: root of the scrapes
  66. out_path: place to put any output from the operators
  67. Returns:
  68. None
  69. @TODO(jhaas): this method will likely change, to provide a robust and
  70. well-defined way of chaining operators, applying operators conditionally,
  71. and full-featured scripting of the operator chain. There also needs
  72. to be better definition of the output; right now it's to stdout and
  73. a log.txt file, with operator-dependent images saved for error output
  74. """
  75. if root_path is None:
  76. # default save path is "scrapes" off the current root
  77. root_path = os.path.join(os.path.split(__file__)[0], "Scrapes")
  78. if out_path is None:
  79. out_path = os.path.join(os.path.split(__file__)[0], "Compares")
  80. if type(base) in types.StringTypes: base = (base, None)
  81. if type(compare) in types.StringTypes: compare = (compare, None)
  82. if type(ops) in types.StringTypes: ops = [ops]
  83. base_dir = os.path.join(root_path, base[0])
  84. compare_dir = os.path.join(root_path, compare[0])
  85. if base[1] is None:
  86. # base defaults to earliest capture
  87. base = (base[0], max(os.listdir(base_dir)))
  88. if compare[1] is None:
  89. # compare defaults to latest capture
  90. compare = (compare[0], min(os.listdir(compare_dir)))
  91. out_path = os.path.join(out_path, base[0], base[1], compare[0], compare[1])
  92. drivers.windowing.PreparePath(out_path)
  93. # TODO(jhaas): right now we're just dumping output to a log file
  94. # (and the console), which works as far as it goes but isn't nearly
  95. # robust enough. Change this after deciding exactly what we want to
  96. # change it to.
  97. out_file = open(os.path.join(out_path, "log.txt"), "w")
  98. description_string = ("Comparing %s %s to %s %s" %
  99. (base[0], base[1], compare[0], compare[1]))
  100. out_file.write(description_string)
  101. print(description_string)
  102. base_dir = os.path.join(base_dir, base[1])
  103. compare_dir = os.path.join(compare_dir, compare[1])
  104. for filename in os.listdir(base_dir):
  105. out_file.write("%s: " % filename)
  106. if not os.path.isfile(os.path.join(compare_dir, filename)):
  107. out_file.write("Does not exist in target directory\n")
  108. print("File %s does not exist in target directory" % filename)
  109. continue
  110. base_filename = os.path.join(base_dir, filename)
  111. compare_filename = os.path.join(compare_dir, filename)
  112. for op in ops:
  113. if type(op) in types.StringTypes: op = (op, None)
  114. module = operators.GetOperator(op[0])
  115. ret = module.Compare(base_filename, compare_filename)
  116. if ret is None:
  117. print("%s: OK" % (filename,))
  118. out_file.write("OK\n")
  119. else:
  120. print("%s: %s" % (filename, ret[0]))
  121. out_file.write("%s\n" % (ret[0]))
  122. ret[1].save(os.path.join(out_path, filename))
  123. out_file.close()
  124. def main():
  125. """Main executable. Parse the command line and invoke the command."""
  126. cmdline = command_line.CommandLine()
  127. # The below two commands are currently unstable so have been disabled
  128. # commands.compare2.CreateCommand(cmdline)
  129. # commands.maskmaker.CreateCommand(cmdline)
  130. commands.measure.CreateCommand(cmdline)
  131. commands.scrape.CreateCommand(cmdline)
  132. cmdline.ParseCommandLine()
  133. return 0
  134. if __name__ == "__main__":
  135. sys.exit(main())