def run_test_set(self, test_set): """Run all ``.webtest`` files in the given `TestSet`. """ for filename in test_set.filenames: if WebtestRunner.verbosity != 'error': log("==== Executing: %s ==========" % filename) self._run_webtest_file(filename) # Sleep between scenarios grinder.sleep(WebtestRunner.scenario_think_time)
def execute(self, test, wrapper, request): """Execute a Grinder `Test` instance, wrapped in ``wrapper``, that sends a `~webtest.parser.Request`. """ if WebtestRunner.verbosity != 'error': log("------ Test %d: %s" % (test.getNumber(), request)) # Evaluate any expressions in the request URL url = self.eval_expressions(request.url) # Evaluate expressions in parameters and headers, and # convert them to NVPairs parameters = self.evaluated_nvpairs(request.parameters) headers = self.evaluated_nvpairs(request.headers) # Send a POST or GET to the wrapped HTTPRequest if request.method == 'POST': # If the request has a body, use that if request.body: body = self.eval_expressions(request.body) response = wrapper.POST(url, body, headers) # Otherwise, pass the form parameters else: response = wrapper.POST(url, parameters, headers) elif request.method == 'GET': response = wrapper.GET(url, parameters, headers) else: message = "Unknown HTTP method: '%s'" % request.method message += " in request defined on line %d" % request.line_number raise BadRequestMethod(message) if WebtestRunner.verbosity == 'debug': log("------ Response from %s: ------" % request) self.log_response(response) # If request has a 'Capture' attribute, parse it if request.capture: self.eval_capture(request, response) return response
def log_response(self, response): """Output full response information to the log file. """ log("HEADERS:") for name in response.listHeaders(): value = response.getHeader(name) log(" %s: %s" % (name, value)) log("BODY:") body = response.getText() # Prettify xml if body.startswith('<?xml'): # This helps with making the XML more readable, but gets UTF-8 # errors from time to time. If they occur, ignore them and just # skip prettification import xml.dom.minidom try: body = body.replace('\n', '').replace('\r', '').replace('\t', '') body = xml.dom.minidom.parseString(body).toprettyxml() except: pass log(body)
def eval_capture(self, request, response): """Evaluate any ``Capture`` expressions in the given request, and set variables to matching text in the given response. Return the number of capture expressions that were successfully evaluated. In order for this to work, you should include a ``Capture`` element inside the ``Request`` element whose response you want to capture. Each expression inside ``Capture`` should follow this format:: {VAR_NAME = regexp} Where ``regexp`` is a regular expression with parentheses around the part you want to capture (leave out the parentheses to capture the entire match). For example, if your response contains:: ... <a href="http://python.org"> ... And you want to store the URL into a variable called ``HREF``, do:: {HREF = <a href="([^"]+)">} If any capture expression is not found in the response, a `CaptureFailed` is raised. This makes them useful for verification too--if you want to ensure that a response contains expected text, just include a capture expression that looks for it. In this case, you can leave out the parentheses, since you don't need to capture any particular part, and if you don't need to keep the value for later, you can just assign it to a dummy variable. For example, to verify that the response includes a form with a "submit" button:: {VERIFY = <form.*>.*<input type="submit".*>.*</form>} You can include multiple ``{VAR_NAME = regexp}`` expressions in the same ``Capture`` element, to capture several variables from the same response, or to do several verifications. Since regular expressions often contain characters that are precious to XML, such as ``< > &`` and so on, you can enclose your capture expressions in a ``CDATA`` block to prevent them from being interpreted as XML:: <Request ...> ... <Capture> <![CDATA[ {VAR_A = <a href="([^"]+)">} {VAR_B = <b>([^<]+)</b>)} ]]> </Capture> </Request> You can include ``{VAR_NAME}`` references in the right-hand side of the expression; for example, if you have previously assigned a value to ``ORDER_NUMBER``, and you want to capture the contents of a ``div`` having that order number as its ``id``, you could do:: {ORDER_DIV = <div id="{ORDER_NUMBER}">(.*)</div>} If ``ORDER_NUMBER`` was previously set to ``12345``, then the above will be expanded to:: {ORDER_DIV = <div id="12345">(.*)</div>} before matching in the response body. """ # Number of successful captures captured = 0 # If capture expression is empty, there's nothing to do if not request.captures(): return captured # Import re here to avoid threading problems # See: http://osdir.com/ml/java.grinder.user/2003-07/msg00030.html import re # Match a {VAR_NAME = <regular expression>} re_capture = re.compile('^{([_A-Z0-9]+) ?= ?(.+)}$') # Get response body body = str(response.getText()) # Evaluate each {...} expression found in the list of request.captures() for expression in request.captures(): # Error if this expression doesn't look like a capture match = re_capture.search(expression) if not match: message = "Syntax error in capture expression '%s'" % expression message += " in request defined on line %d" % request.line_number raise SyntaxError(message) # Get the two parts of the capture expression name, value = match.groups() # Expand any {VAR} expressions before evaluating the regexp regexp = self.eval_expressions(value) if WebtestRunner.verbosity in ('debug', 'info'): log("Looking in response for match to regexp: %s" % regexp) # Error if the regexp doesn't match part of the response body match = re.search(regexp, body) if not match: log("!!!!!! No match for %s" % regexp) log("!!!!!! In request defined on line %d" % request.line_number) log("!!!!!! Response body:") log(body) raise CaptureFailed("No match for %s" % regexp) # Set the given variable name to the first parenthesized expression if match.groups(): value = match.group(1) # or the entire match if there was no parenthesized expression else: value = match.group(0) captured += 1 if WebtestRunner.verbosity in ('debug', 'info'): log("Captured %s = %s" % (name, value)) self.variables[name] = value return captured
def eval_expressions(self, value): """Parse the given string for variables or macros, and do any necessary variable assignment. Return the string with all expressions expanded. Allowed expressions: ``{MY_VAR}`` Expand to the current value of ``MY_VAR`` ``{MY_VAR = literal}`` Assign ``MY_VAR`` a literal value, and expand to that value ``{macro_name(args)}`` Expand to the result of ``macro_name(args)`` ``{MY_VAR = macro_name(args)}`` Assign ``MY_VAR`` the result of calling ``macro_name(args)``, and also expand to the resulting value. See the `webtest.macro` module for more information. Any given value that does not match any of these forms is simply returned as-is. If you need to use literal ``{`` or ``}`` characters in a string, precede them with a backslash, like ``\\{`` or ``\\}``. The given value may contain multiple ``{...}`` expressions, and may have additional text before or after any expression. For example, if you have previously assigned two variables ``FOO`` and ``BAR``: >>> eval_expressions('{FOO = Hello}') 'Hello' >>> eval_expressions('{BAR = world}') 'world' you can combine them in an expression like this: >>> eval_expressions('{FOO} {BAR}!') 'Hello world!' The only caveat is that a ``{...}`` expression may not contain another ``{...}`` expression inside it. """ # Regular expressions to match in eval_expressions # Import re here to avoid threading problems # See: http://osdir.com/ml/java.grinder.user/2003-07/msg00030.html import re # Match the first {...} expression re_expansion = re.compile(r'((?:[^{\\]|\\.)*){((?:[^}\\]|\\.)*)}(.*)') # VAR_NAME=macro(args) re_var_macro = re.compile('([_A-Z0-0-99]+) ?= ?([_a-z]+)\(([^)]*)\)') # VAR_NAME=literal re_var_literal = re.compile('([_A-Z0-9]+) ?= ?([^}]+)') # macro(args) re_macro = re.compile('([_a-z]+)\(([^)]*)\)') # VAR_NAME re_var = re.compile('^([_A-Z0-9]+)$') macro = WebtestRunner.macro_class() # Match and replace until no {...} expressions are found to_expand = re_expansion.match(value) while to_expand: before, expression, after = to_expand.groups() # VAR_NAME=macro(args) if re_var_macro.match(expression): name, macro_name, args = re_var_macro.match(expression).groups() expanded = self.variables[name] = macro.invoke(macro_name, args) # VAR_NAME=literal elif re_var_literal.match(expression): name, literal = re_var_literal.match(expression).groups() self.variables[name] = literal expanded = literal # macro(args) elif re_macro.match(expression): macro_name, args = re_macro.match(expression).groups() expanded = macro.invoke(macro_name, args) # VAR_NAME elif re_var.match(expression): name = re_var.match(expression).groups()[0] if name in self.variables: expanded = self.variables[name] else: raise NameError("Variable '%s' is not initialized!" % name) # Invalid expression else: raise SyntaxError( "Syntax error '%s' in value '%s'" % (expression, value)) if WebtestRunner.verbosity in ('debug', 'info'): log("%s => '%s'" % (expression, expanded)) # Assemble the expanded value and check for another match value = before + expanded + after to_expand = re_expansion.match(value) return value
def eval_expressions(self, value): """Parse the given string for variables or macros, and do any necessary variable assignment. Return the string with all expressions expanded. Allowed expressions: ``{MY_VAR}`` Expand to the current value of ``MY_VAR`` ``{MY_VAR = literal}`` Assign ``MY_VAR`` a literal value, and expand to that value ``{macro_name(args)}`` Expand to the result of ``macro_name(args)`` ``{MY_VAR = macro_name(args)}`` Assign ``MY_VAR`` the result of calling ``macro_name(args)``, and also expand to the resulting value. See the `webtest.macro` module for more information. Any given value that does not match any of these forms is simply returned as-is. If you need to use literal ``{`` or ``}`` characters in a string, precede them with a backslash, like ``\\{`` or ``\\}``. The given value may contain multiple ``{...}`` expressions, and may have additional text before or after any expression. For example, if you have previously assigned two variables ``FOO`` and ``BAR``: >>> eval_expressions('{FOO = Hello}') 'Hello' >>> eval_expressions('{BAR = world}') 'world' you can combine them in an expression like this: >>> eval_expressions('{FOO} {BAR}!') 'Hello world!' The only caveat is that a ``{...}`` expression may not contain another ``{...}`` expression inside it. """ # Regular expressions to match in eval_expressions # Import re here to avoid threading problems # See: http://osdir.com/ml/java.grinder.user/2003-07/msg00030.html import re # Match the first {...} expression re_expansion = re.compile(r'((?:[^{\\]|\\.)*){((?:[^}\\]|\\.)*)}(.*)') # VAR_NAME=macro(args) re_var_macro = re.compile('([_A-Z0-0-99]+) ?= ?([_a-z]+)\(([^)]*)\)') # VAR_NAME=literal re_var_literal = re.compile('([_A-Z0-9]+) ?= ?([^}]+)') # macro(args) re_macro = re.compile('([_a-z]+)\(([^)]*)\)') # VAR_NAME re_var = re.compile('^([_A-Z0-9]+)$') macro = WebtestRunner.macro_class() # Match and replace until no {...} expressions are found to_expand = re_expansion.match(value) while to_expand: before, expression, after = to_expand.groups() # VAR_NAME=macro(args) if re_var_macro.match(expression): name, macro_name, args = re_var_macro.match( expression).groups() expanded = self.variables[name] = macro.invoke( macro_name, args) # VAR_NAME=literal elif re_var_literal.match(expression): name, literal = re_var_literal.match(expression).groups() self.variables[name] = literal expanded = literal # macro(args) elif re_macro.match(expression): macro_name, args = re_macro.match(expression).groups() expanded = macro.invoke(macro_name, args) # VAR_NAME elif re_var.match(expression): name = re_var.match(expression).groups()[0] if name in self.variables: expanded = self.variables[name] else: raise NameError("Variable '%s' is not initialized!" % name) # Invalid expression else: raise SyntaxError("Syntax error '%s' in value '%s'" % (expression, value)) if WebtestRunner.verbosity in ('debug', 'info'): log("%s => '%s'" % (expression, expanded)) # Assemble the expanded value and check for another match value = before + expanded + after to_expand = re_expansion.match(value) return value