Skip to content

Commit

Permalink
new: added run_id to reports and to the ctx object (#40)
Browse files Browse the repository at this point in the history
  • Loading branch information
cryptaliagy committed Jul 13, 2021
1 parent c21f934 commit 13292d2
Show file tree
Hide file tree
Showing 9 changed files with 153 additions and 44 deletions.
7 changes: 6 additions & 1 deletion src/pytest_quilla/__init__.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import uuid

import pytest
from _pytest.config import Config
from _pytest.config.argparsing import Parser
Expand Down Expand Up @@ -37,4 +39,7 @@ def pytest_load_initial_conftests(early_config: Config, parser: Parser):


def pytest_collect_file(parent: pytest.Session, path):
return collect_file(parent, path, parent.config.getini('quilla-prefix'))
return collect_file(parent, path, parent.config.getini('quilla-prefix'), run_id)


run_id = str(uuid.uuid4())
25 changes: 21 additions & 4 deletions src/pytest_quilla/pytest_classes.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,14 +10,15 @@
from quilla.reports.report_summary import ReportSummary


def collect_file(parent: pytest.Session, path: LocalPath, prefix: str):
def collect_file(parent: pytest.Session, path: LocalPath, prefix: str, run_id: str):
'''
Collects files if their path ends with .json and starts with the prefix
Args:
parent: The session object performing the collection
path: The path to the file that might be collected
prefix: The prefix for files that should be collected
run_id: The run ID of the quilla tests
Returns:
A quilla file object if the path matches, None otherwise
Expand All @@ -26,10 +27,14 @@ def collect_file(parent: pytest.Session, path: LocalPath, prefix: str):
# TODO: change "path" to be "fspath" when pytest 6.3 is released:
# https://docs.pytest.org/en/latest/_modules/_pytest/hookspec.html#pytest_collect_file
if path.ext == '.json' and path.basename.startswith(prefix):
return QuillaFile.from_parent(parent, fspath=path)
return QuillaFile.from_parent(parent, fspath=path, run_id=run_id)


class QuillaFile(pytest.File):
def __init__(self, *args, run_id: str = '', **kwargs) -> None:
super().__init__(*args, **kwargs)
self.quilla_run_id = run_id

def collect(self):
'''
Loads the JSON test data from the path and creates the test instance
Expand All @@ -38,13 +43,19 @@ def collect(self):
A quilla item configured from the JSON data
'''
test_data = self.fspath.open().read()
yield QuillaItem.from_parent(self, name=self.fspath.purebasename, test_data=test_data)
yield QuillaItem.from_parent(
self,
name=self.fspath.purebasename,
test_data=test_data,
run_id=self.quilla_run_id
)


class QuillaItem(pytest.Item):
def __init__(self, name: str, parent: QuillaFile, test_data: str):
def __init__(self, name: str, parent: QuillaFile, test_data: str, run_id: str):
super(QuillaItem, self).__init__(name, parent)
self.test_data = test_data
self.quilla_run_id = run_id
json_data = json.loads(test_data)
markers = json_data.get('markers', [])
for marker in markers:
Expand All @@ -59,6 +70,12 @@ def runtest(self):
[*self.config.getoption('--quilla-opts').split(), ''],
str(self.config.rootpath)
)
if not (
'-i' in self.config.getoption('--quilla-opts') or
'--run-id' in self.config.getoption('--quilla-opts')
):
ctx.run_id = self.quilla_run_id

ctx.json = self.test_data
results = execute(ctx)
self.results = results
Expand Down
75 changes: 49 additions & 26 deletions src/quilla/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,13 +30,20 @@ def make_parser() -> argparse.ArgumentParser: # pragma: no cover
'''
parser = argparse.ArgumentParser(
prog='quilla',
usage='%(prog)s [options] [-f] JSON',
description='''
Program to provide a report of UI validations given a json representation
of the validations or given the filename containing a json document describing
the validations
''',
)

parser.add_argument(
'--version',
action='store_true',
help='Prints the version of the software and quits'
)

parser.add_argument(
'-f',
'--file',
Expand All @@ -48,25 +55,32 @@ def make_parser() -> argparse.ArgumentParser: # pragma: no cover
'json',
help='The json file name or raw json string',
)
parser.add_argument(
'--debug',
action='store_true',
help='Enable debug mode',

config_group = parser.add_argument_group(title='Configuration options')
config_group.add_argument(
'-i',
'--run-id',
action='store',
metavar='run_id',
default=None,
help='A run ID for quilla, if manually passed in.'
'Used to set many quilla tests to have the same run ID'
)
parser.add_argument(
config_group.add_argument(
'-d',
'--definitions',
action='append',
metavar='file',
help='A file with definitions for the \'Definitions\' context object'
)
config_group.add_argument(
'--driver-dir',
dest='drivers_path',
action='store',
default='.',
help='The directory where browser drivers are stored',
)
parser.add_argument(
'-P',
'--pretty',
action='store_true',
help='Set this flag to have the output be pretty-printed'
)
parser.add_argument(
config_group.add_argument(
'--no-sandbox',
dest='no_sandbox',
action='store_true',
Expand All @@ -75,26 +89,35 @@ def make_parser() -> argparse.ArgumentParser: # pragma: no cover
Useful for running in docker containers'
'''
)
parser.add_argument(
'-d',
'--definitions',
action='append',
metavar='file',
help='A file with definitions for the \'Definitions\' context object'

output_group = parser.add_argument_group(title='Output Options')
output_group.add_argument(
'-P',
'--pretty',
action='store_true',
help='Set this flag to have the output be pretty-printed'
)
parser.add_argument(
output_group.add_argument(
'--indent',
type=int,
default=4,
help='How much space each indent level should have when pretty-printing the report'
)

debug_group = parser.add_argument_group(title='Debug Options')
debug_group.add_argument(
'--debug',
action='store_true',
help='Enable debug mode',
)
debug_group.add_argument(
'-v',
'--verbose',
action='count',
help='Flag to increase the verbosity of the outputs. '
'Log outputs are directed to stderr by default.',
default=0
)
parser.add_argument(
'--version',
action='store_true',
help='Prints the version of the software and quits'
)

return parser

Expand Down Expand Up @@ -257,6 +280,8 @@ def setup_context(args: List[str], plugin_root: str = '.') -> Context:
parsed_args.no_sandbox,
parsed_args.definitions,
logger=logger,
run_id=parsed_args.run_id,
indent=parsed_args.indent,
)

logger.info('Running "quilla_configure" hook')
Expand All @@ -279,8 +304,6 @@ def run():
ctx.logger.debug('Finished generating reports')

out = reports.to_dict()
if ctx._context_data['Outputs']:
out['Outputs'] = ctx._context_data['Outputs']

if ctx.pretty:
print(json.dumps(
Expand Down
29 changes: 26 additions & 3 deletions src/quilla/ctx.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
NullHandler,
)
import json
import uuid

from pluggy import PluginManager
import pydeepmerge as pdm
Expand All @@ -39,6 +40,7 @@ class Context(DriverHolder):
is_file: Whether a file was originally passed in or if the raw json was passed in
no_sandbox: Whether to pass the '--no-sandbox' arg to Chrome and Edge
logger: An optional configured logger instance.
run_id: A string that uniquely identifies the run of Quilla.
Attributes:
Expand All @@ -54,6 +56,8 @@ class Context(DriverHolder):
no_sandbox: Whether to pass the '--no-sandbox' arg to Chrome and Edge
logger: A logger instance. If None was passed in for the 'logger' argument, will create
one with the default logger.
run_id: A string that uniquely identifies the run of Quilla.
pretty_print_indent: How many spaces to use for indentation when pretty-printing the output
'''
default_context: Optional['Context'] = None
_drivers_path: str
Expand All @@ -65,7 +69,6 @@ class Context(DriverHolder):
r'([a-zA-Z][a-zA-Z0-9_]+)(\.[a-zA-Z_][a-zA-Z0-9_]+)+'
)
_output_browser: str = 'Firefox'
pretty_print_indent: int = 4

def __init__(
self,
Expand All @@ -77,7 +80,9 @@ def __init__(
is_file: bool = False,
no_sandbox: bool = False,
definitions: List[str] = [],
logger: Optional[Logger] = None
logger: Optional[Logger] = None,
run_id: Optional[str] = None,
indent: int = 4,
):
super().__init__()
self.pm = plugin_manager
Expand All @@ -87,6 +92,7 @@ def __init__(
self.json = json_data
self.is_file = is_file
self.no_sandbox = no_sandbox
self.pretty_print_indent = indent
path = Path(drivers_path)

if logger is None:
Expand All @@ -95,10 +101,22 @@ def __init__(
else:
self.logger = logger

if run_id is None:
self.run_id = str(uuid.uuid4()) # Generate a random UUID
else:
self.run_id = run_id

self.drivers_path = str(path.resolve())
self._context_data: Dict[str, dict] = {'Validation': {}, 'Outputs': {}, 'Definitions': {}}
self._load_definition_files(definitions)

@property
def outputs(self) -> dict:
'''
A dictionary of all outputs created by the steps for the current Quilla test
'''
return self._context_data['Outputs']

@property
def is_debug(self) -> bool:
'''
Expand Down Expand Up @@ -329,7 +347,9 @@ def get_default_context(
no_sandbox: bool = False,
definitions: List[str] = [],
recreate_context: bool = False,
logger: Optional[Logger] = None
logger: Optional[Logger] = None,
run_id: Optional[str] = None,
indent: int = 4,
) -> Context:
'''
Gets the default context, creating a new one if necessary.
Expand All @@ -350,6 +370,7 @@ def get_default_context(
recreate_context: Whether a new context object should be created or not
logger: An optional logger instance. If None, one will be created
with the NullHandler.
run_id: A string that uniquely identifies the run of Quilla.
Returns
Application context shared for the entire application
Expand All @@ -368,5 +389,7 @@ def get_default_context(
no_sandbox,
definitions,
logger,
run_id,
indent,
)
return Context.default_context
4 changes: 2 additions & 2 deletions src/quilla/reports/base_report.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import json
from abc import (
abstractclassmethod,
abstractmethod,
)
from typing import Dict
Expand Down Expand Up @@ -29,7 +28,8 @@ def __init__(self, report_type: ReportType, browser: str, action: UITestActions,
self.msg: str = msg
self.report_type: ReportType = report_type

@abstractclassmethod
@classmethod
@abstractmethod
def from_dict(cls, report: Dict[str, Dict[str, str]]) -> 'BaseReport':
'''
Converts a dictionary report into a valid Report object
Expand Down
Loading

0 comments on commit 13292d2

Please sign in to comment.