diff options
Diffstat (limited to 'tools/site_compare/site_compare.py')
-rw-r--r-- | tools/site_compare/site_compare.py | 202 |
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 |