summaryrefslogtreecommitdiffstats
path: root/tools/site_compare/site_compare.py
diff options
context:
space:
mode:
Diffstat (limited to 'tools/site_compare/site_compare.py')
-rw-r--r--tools/site_compare/site_compare.py202
1 files changed, 202 insertions, 0 deletions
diff --git a/tools/site_compare/site_compare.py b/tools/site_compare/site_compare.py
new file mode 100644
index 0000000..8acfdcf
--- /dev/null
+++ b/tools/site_compare/site_compare.py
@@ -0,0 +1,202 @@
+#!/usr/bin/python2.4
+# Copyright 2008, Google Inc.
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are
+# met:
+#
+# * Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+# * Redistributions in binary form must reproduce the above
+# copyright notice, this list of conditions and the following disclaimer
+# in the documentation and/or other materials provided with the
+# distribution.
+# * Neither the name of Google Inc. nor the names of its
+# contributors may be used to endorse or promote products derived from
+# this software without specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+"""SiteCompare component to handle bulk scrapes.
+
+Invokes a list of browsers and sends them to a list of URLs,
+saving the rendered results to a specified directory, then
+performs comparison operations on the resulting bitmaps and
+saves the results
+"""
+
+
+# This line is necessary to work around a QEMU bug
+import _imaging
+
+import os # Functions for walking the directory tree
+import types # Runtime type-checking
+
+import command_line # command-line parsing
+import drivers # Functions for driving keyboard/mouse/windows, OS-specific
+import operators # Functions that, given two bitmaps as input, produce
+ # output depending on the performance of an operation
+import scrapers # Functions that know how to capture a render from
+ # particular browsers
+
+import commands.compare2 # compare one page in two versions of same browser
+import commands.maskmaker # generate a mask based on repeated scrapes
+import commands.measure # measure length of time a page takes to load
+import commands.scrape # scrape a URL or series of URLs to a bitmap
+
+# The timeload command is obsolete (too flaky); it may be reinstated
+# later but for now it's been superceded by "measure"
+# import commands.timeload # measure length of time a page takes to load
+
+def Scrape(browsers, urls, window_size=(1024, 768),
+ window_pos=(0, 0), timeout=20, save_path=None, **kwargs):
+ """Invoke one or more browsers over one or more URLs, scraping renders.
+
+ Args:
+ browsers: browsers to invoke with optional version strings
+ urls: URLs to visit
+ window_size: size of the browser window to display
+ window_pos: location of browser window
+ timeout: time (in seconds) to wait for page to load
+ save_path: root of save path, automatically appended with browser and
+ version
+ kwargs: miscellaneous keyword args, passed to scraper
+ Returns:
+ None
+
+ @TODO(jhaas): more parameters, or perhaps an indefinite dictionary
+ parameter, for things like length of time to wait for timeout, speed
+ of mouse clicks, etc. Possibly on a per-browser, per-URL, or
+ per-browser-per-URL basis
+ """
+
+ if type(browsers) in types.StringTypes: browsers = [browsers]
+
+ if save_path is None:
+ # default save path is "scrapes" off the current root
+ save_path = os.path.join(os.path.split(__file__)[0], "Scrapes")
+
+ for browser in browsers:
+ # Browsers should be tuples of (browser, version)
+ if type(browser) in types.StringTypes: browser = (browser, None)
+ scraper = scrapers.GetScraper(browser)
+
+ full_path = os.path.join(save_path, browser[0], scraper.version)
+ drivers.windowing.PreparePath(full_path)
+
+ scraper.Scrape(urls, full_path, window_size, window_pos, timeout, kwargs)
+
+
+def Compare(base, compare, ops, root_path=None, out_path=None):
+ """Compares a series of scrapes using a series of operators.
+
+ Args:
+ base: (browser, version) tuple of version to consider the baseline
+ compare: (browser, version) tuple of version to compare to
+ ops: list of operators plus operator arguments
+ root_path: root of the scrapes
+ out_path: place to put any output from the operators
+
+ Returns:
+ None
+
+ @TODO(jhaas): this method will likely change, to provide a robust and
+ well-defined way of chaining operators, applying operators conditionally,
+ and full-featured scripting of the operator chain. There also needs
+ to be better definition of the output; right now it's to stdout and
+ a log.txt file, with operator-dependent images saved for error output
+ """
+ if root_path is None:
+ # default save path is "scrapes" off the current root
+ root_path = os.path.join(os.path.split(__file__)[0], "Scrapes")
+
+ if out_path is None:
+ out_path = os.path.join(os.path.split(__file__)[0], "Compares")
+
+ if type(base) in types.StringTypes: base = (base, None)
+ if type(compare) in types.StringTypes: compare = (compare, None)
+ if type(ops) in types.StringTypes: ops = [ops]
+
+ base_dir = os.path.join(root_path, base[0])
+ compare_dir = os.path.join(root_path, compare[0])
+
+ if base[1] is None:
+ # base defaults to earliest capture
+ base = (base[0], max(os.listdir(base_dir)))
+
+ if compare[1] is None:
+ # compare defaults to latest capture
+ compare = (compare[0], min(os.listdir(compare_dir)))
+
+ out_path = os.path.join(out_path, base[0], base[1], compare[0], compare[1])
+ drivers.windowing.PreparePath(out_path)
+
+ # TODO(jhaas): right now we're just dumping output to a log file
+ # (and the console), which works as far as it goes but isn't nearly
+ # robust enough. Change this after deciding exactly what we want to
+ # change it to.
+ out_file = open(os.path.join(out_path, "log.txt"), "w")
+ description_string = ("Comparing %s %s to %s %s" %
+ (base[0], base[1], compare[0], compare[1]))
+ out_file.write(description_string)
+ print description_string
+
+ base_dir = os.path.join(base_dir, base[1])
+ compare_dir = os.path.join(compare_dir, compare[1])
+
+ for filename in os.listdir(base_dir):
+ out_file.write("%s: " % filename)
+
+ if not os.path.isfile(os.path.join(compare_dir, filename)):
+ out_file.write("Does not exist in target directory\n")
+ print "File %s does not exist in target directory" % filename
+ continue
+
+ base_filename = os.path.join(base_dir, filename)
+ compare_filename = os.path.join(compare_dir, filename)
+
+ for op in ops:
+ if type(op) in types.StringTypes: op = (op, None)
+
+ module = operators.GetOperator(op[0])
+
+ ret = module.Compare(base_filename, compare_filename)
+ if ret is None:
+ print "%s: OK" % (filename,)
+ out_file.write("OK\n")
+ else:
+ print "%s: %s" % (filename, ret[0])
+ out_file.write("%s\n" % (ret[0]))
+ ret[1].save(os.path.join(out_path, filename))
+
+ out_file.close()
+
+
+def main():
+ """Main executable. Parse the command line and invoke the command."""
+ cmdline = command_line.CommandLine()
+
+ # The below two commands are currently unstable so have been disabled
+ # commands.compare2.CreateCommand(cmdline)
+ # commands.maskmaker.CreateCommand(cmdline)
+ commands.measure.CreateCommand(cmdline)
+ commands.scrape.CreateCommand(cmdline)
+
+ cmdline.ParseCommandLine()
+
+
+
+if __name__ == "__main__":
+ main()
+ \ No newline at end of file