├── .github └── workflows │ └── python-app.yml ├── .gitignore ├── .travis.yml ├── JSONLibrary ├── __init__.py ├── __version__.py └── jsonlibrary.py ├── MANIFEST.in ├── README.md ├── UNLICENSE ├── acceptance └── JSONLibrary.robot ├── docs ├── JSONLibrary.html └── index.html ├── requirements-dev.txt ├── requirements.txt ├── setup.py ├── tasks.py ├── tests ├── .coveragerc ├── __init__.py ├── json │ ├── broken_schema.json │ ├── example.json │ └── example_schema.json └── test_JSONLibrary.py └── tox.ini /.github/workflows/python-app.yml: -------------------------------------------------------------------------------- 1 | # This workflow will install Python dependencies, run tests and lint with a single version of Python 2 | # For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-python 3 | 4 | name: Python application 5 | 6 | on: pull_request 7 | 8 | permissions: 9 | contents: read 10 | 11 | jobs: 12 | validate_unix: 13 | 14 | runs-on: ubuntu-latest 15 | strategy: 16 | matrix: 17 | python-version: ["3.8", "3.9"] 18 | steps: 19 | - name: checkout 20 | uses: actions/checkout@v3 21 | 22 | - name: Set up Python ${{ matrix.python-version }} 23 | uses: actions/setup-python@v3 24 | with: 25 | python-version: ${{ matrix.python-version }} 26 | 27 | - name: Install dependencies 28 | run: | 29 | python -m pip install --upgrade pip 30 | python -m pip install -e . 31 | python -m pip install -r requirements-dev.txt 32 | 33 | - name: check pylint 34 | run: pylint JSONLibrary --disable=R,C,W0703,W0212,W1203 35 | 36 | - name: check doc generation 37 | run: python -m robot.libdoc JSONLibrary docs/JSONLibrary.html 38 | 39 | - name: check black 40 | run: black . --check --diff 41 | 42 | - name: Lint with flake8 43 | run: | 44 | # stop the build if there are Python syntax errors or undefined names 45 | flake8 . --count --select=E9,F63,F7,F82 --show-source --statistics 46 | # exit-zero treats all errors as warnings. The GitHub editor is 127 chars wide 47 | flake8 . --count --exit-zero --max-complexity=10 --max-line-length=127 --statistics 48 | 49 | - name: validate 50 | run: | 51 | pytest --cov-config=tests/.coveragerc --cov --cov-report term tests/ 52 | coverage xml --rcfile tests/.coveragerc 53 | coverage html --rcfile tests/.coveragerc 54 | robot -d tests/__out__/robot acceptance/ 55 | 56 | validate_windows: 57 | 58 | runs-on: windows-latest 59 | strategy: 60 | matrix: 61 | python-version: ["3.10", "3.11"] 62 | steps: 63 | - name: checkout 64 | uses: actions/checkout@v3 65 | 66 | - name: Set up Python ${{ matrix.python-version }} 67 | uses: actions/setup-python@v3 68 | with: 69 | python-version: ${{ matrix.python-version }} 70 | 71 | - name: Install dependencies 72 | run: | 73 | python -m pip install --upgrade pip 74 | python -m pip install -e . 75 | python -m pip install -r requirements-dev.txt 76 | 77 | - name: validate 78 | run: | 79 | pytest --cov-config=tests/.coveragerc --cov --cov-report term tests/ 80 | coverage xml --rcfile tests/.coveragerc 81 | coverage html --rcfile tests/.coveragerc 82 | robot -d tests/__out__/robot acceptance/ 83 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.pyc 2 | dist 3 | .idea 4 | .cache 5 | .coverage 6 | *egg* 7 | build 8 | htmlcov 9 | result 10 | .tox 11 | .pytest_cache 12 | tests/__out__ 13 | # PyDev/RED 14 | .settings 15 | .project 16 | .pydevproject 17 | __pycache__ 18 | red.xml 19 | /libspecs/ 20 | /Results/ 21 | 22 | #pipenv/virtualenv 23 | .venv 24 | Pipfile 25 | Pipfile.lock 26 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: python 2 | matrix: 3 | include: 4 | - python: 3.8 5 | env: TOXENV=py38 6 | - python: 3.9 7 | env: TOXENV=py39 8 | install: "pip install -r requirements-dev.txt" 9 | script: 10 | - invoke style-check 11 | - invoke lint 12 | - invoke test 13 | - invoke docs 14 | deploy: 15 | provider: pypi 16 | user: nottyo 17 | password: 18 | secure: mFvownc1BCD+vLkz9oK/wV3Mw3FSdmPHCV000q68DbXKaEmF0QBGgNjkn0s4LEXj7X4asYNJF8BCCnzQrgiOMtE9p+PmYhQMFuxqphEnrmmYsDOSjTU6JBdRwuyoUN8f704V9aswBgnMJb1xpLpu6loUVV0+2VAfG82z9uQH8t86Hg2/4QhJIoepzHvDmIk3YPnMcS3vAsHh3WbeLmgYk3c1aQQdES7bJGWyex5bo4C2gCACqEDfMeP0bqEtoTB5+dPtk4LsJUa/OSZz8BMl4LYuQdUpbmRgpfpdOWZygOaivUisWjgElGtlEsl1koWHz1KR6TpfzNVi9Sz9SrwibTnUyLCQdfmj/4oPfFER41PFgFdvbYe3IiPIeNKgQ2yzrnbL6vyeh27fbefsaJTJejd/eDvq78TF5E2YK/n200dUmejd0ISiW6JvySs7LkWeDlyPPzThc1Kg1rzii5MTsFV0UENmzdP6bspqNMOP6v9VEyxlTGfFmZwfTMc0jq2fWbww0CSE5QacFZts0xmi9x4syYaZooL7oCYo8ZIUdonXMbaVG4Wz11nsedbvSDS5zWswqVd+Kvj57kmtokzS0eCzN4u59slhdLpKYzSnUW+o/d5BxSJwOB2MLjth60yOj0TUEstu2mAiKY59pGH0nhiaSR5pKU6Rz5yXefVzeuA= 19 | on: 20 | tags: true 21 | distributions: dist 22 | repo: robotframework-thailand/robotframework-jsonlibrary 23 | -------------------------------------------------------------------------------- /JSONLibrary/__init__.py: -------------------------------------------------------------------------------- 1 | from .jsonlibrary import JSONLibrary 2 | from .__version__ import __version__ 3 | 4 | __author__ = "Traitanit Huangsri" 5 | __email__ = "traitanit.hua@gmail.com" 6 | -------------------------------------------------------------------------------- /JSONLibrary/__version__.py: -------------------------------------------------------------------------------- 1 | __version__ = "0.5" 2 | -------------------------------------------------------------------------------- /JSONLibrary/jsonlibrary.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | import io 3 | import json 4 | import os.path 5 | import jsonschema 6 | from copy import deepcopy 7 | from robot.api import logger 8 | from robot.utils.asserts import fail 9 | from jsonpath_ng import Index, Fields 10 | from jsonpath_ng.ext import parse as parse_ng 11 | from jsonpath_ng.exceptions import JsonPathParserError 12 | 13 | __author__ = "Traitanit Huangsri" 14 | __email__ = "traitanit.hua@gmail.com" 15 | 16 | 17 | class JSONLibrary: 18 | """JSONLibrary is a robotframework testlibrary for manipulating JSON object (dictionary) 19 | 20 | You can get, add, update and delete your json object using JSONPath. 21 | 22 | == JSONPath Syntax == 23 | | JSONPath | Description | 24 | | $ | the root object/element | 25 | | @ | the current object/element | 26 | | . or [] | child operator | 27 | | .. | recursive descent. JSONPath borrows this syntax from E4X | 28 | | * | wildcard. All objects/element regardless their names. | 29 | | [] | subscript operator. XPath uses it to iterate over element collections and for predicates. 30 | In Javascript and JSON it is the native array operator. | 31 | | [,] | Union operator in XPath results in a combination of node sets. JSONPath allows alternate 32 | names or array indices as a set. | 33 | | [start:end:step] | array slice operator borrowed from ES4 | 34 | | ?() | applies a filter (script) expression. | 35 | | () | script expression, using the underlying script engine. | 36 | 37 | == *** Known issue *** == 38 | If there is a space in JSONPath expression, the module used by this library will throw an exception. 39 | Therefore, please avoid the space in JSONPath expression if possible. 40 | 41 | *Example:* 42 | | JSONPath | Exception? | 43 | | $.[?(@.id == 1)] | Y | 44 | | $.[?(@.id==1)] | N | 45 | | $.[?(@.name=='test 123')] | N | 46 | 47 | == Example Test Cases == 48 | | *** Settings *** | 49 | | Library | JSONLibrary | 50 | | | 51 | | *** Test Cases *** | 52 | | TestManipulatingJSON | 53 | | ${json_object}= | Load JSON From File | example.json | 54 | | ${object_to_add}= | Create Dictionary | country=Thailand | 55 | | ${json_object}= | Add Object To Json | ${json_object} | $..address | ${object_to_add} | 56 | | ${value}= | Get Value From Json | ${json_object} | $..country | 57 | | Should Be Equal As Strings | ${value[0]} | Thailand | 58 | 59 | 60 | """ 61 | 62 | ROBOT_LIBRARY_SCOPE = "GLOBAL" 63 | ROBOT_LIBRARY_DOC_FORMAT = "ROBOT" 64 | ROBOT_EXIT_ON_FAILURE = True 65 | 66 | @staticmethod 67 | def _parse(json_path): 68 | try: 69 | return parse_ng(json_path) 70 | except JsonPathParserError as e: 71 | fail( 72 | "Parser failed to understand syntax '{}'. error message: " 73 | "\n{}\n\nYou may raise an issue on https://github.com/h2non/jsonpath-ng".format( 74 | json_path, e 75 | ) 76 | ) 77 | 78 | @staticmethod 79 | def load_json_from_file(file_name, encoding=None): 80 | """Load JSON from file. 81 | 82 | Return json as a dictionary object. 83 | 84 | Arguments: 85 | - file_name: absolute json file name 86 | - encoding: encoding of the file 87 | 88 | Return json object (list or dictionary) 89 | 90 | Examples: 91 | | ${result}= | Load Json From File | /path/to/file.json | 92 | """ 93 | logger.debug("Check if file exists") 94 | if os.path.isfile(file_name) is False: 95 | logger.error("JSON file: " + file_name + " not found") 96 | raise IOError 97 | with io.open(file_name, mode="r", encoding=encoding) as json_file: 98 | data = json.load(json_file) 99 | return data 100 | 101 | def add_object_to_json(self, json_object, json_path, object_to_add): 102 | """Add an dictionary or list object to json object using json_path 103 | 104 | Arguments: 105 | - json_object: json as a dictionary object. 106 | - json_path: jsonpath expression 107 | - object_to_add: dictionary or list object to add to json_object which is matched by json_path 108 | 109 | Return new json object. 110 | 111 | Examples: 112 | | ${dict}= | Create Dictionary | latitude=13.1234 | longitude=130.1234 | 113 | | ${json}= | Add Object To Json | ${json} | $..address | ${dict} | 114 | """ 115 | json_path_expr = self._parse(json_path) 116 | json_object_cpy = deepcopy(json_object) 117 | object_to_add_cpy = deepcopy(object_to_add) 118 | rv = json_path_expr.find(json_object_cpy) 119 | if len(rv): 120 | for match in rv: 121 | if type(match.value) is dict: 122 | match.value.update(object_to_add_cpy) 123 | if type(match.value) is list: 124 | match.value.append(object_to_add_cpy) 125 | else: 126 | parent_json_path = ".".join(json_path.split(".")[:-1]) 127 | child_name = json_path.split(".")[-1] 128 | json_path_expr = self._parse(parent_json_path) 129 | rv = json_path_expr.find(json_object_cpy) 130 | if len(rv): 131 | for match in rv: 132 | match.value.update({child_name: object_to_add_cpy}) 133 | else: 134 | fail(f"no match found for parent {parent_json_path}") 135 | 136 | return json_object_cpy 137 | 138 | def get_value_from_json(self, json_object, json_path, fail_on_empty=False): 139 | """Get Value From JSON using JSONPath 140 | 141 | Arguments: 142 | - json_object: json as a dictionary object. 143 | - json_path: jsonpath expression 144 | - fail_on_empty: fail the testcases if nothing is returned 145 | 146 | Return array of values 147 | 148 | Examples: 149 | | ${values}= | Get Value From Json | ${json} | $..phone_number | 150 | | ${values}= | Get Value From Json | ${json} | $..missing | fail_on_empty=${True} | 151 | """ 152 | json_path_expr = self._parse(json_path) 153 | rv = json_path_expr.find(json_object) 154 | # optional: make the keyword fails if nothing was return 155 | if fail_on_empty is True and (rv is None or len(rv) == 0): 156 | fail(f"Get Value From Json keyword failed to find a value for {json_path}") 157 | return [match.value for match in rv] 158 | 159 | def update_value_to_json(self, json_object, json_path, new_value): 160 | """Update value to JSON using JSONPath 161 | 162 | Arguments: 163 | - json_object: json as a dictionary object. 164 | - json_path: jsonpath expression 165 | - new_value: value to update 166 | 167 | Return new json_object 168 | 169 | Examples: 170 | | ${json_object}= | Update Value To Json | ${json} | $..address.streetAddress | Ratchadapisek Road | 171 | """ 172 | json_path_expr = self._parse(json_path) 173 | json_object_cpy = deepcopy(json_object) 174 | for match in json_path_expr.find(json_object_cpy): 175 | path = match.path 176 | if isinstance(path, Index): 177 | match.context.value[match.path.index] = new_value 178 | elif isinstance(path, Fields): 179 | match.context.value[match.path.fields[0]] = new_value 180 | return json_object_cpy 181 | 182 | def delete_object_from_json(self, json_object, json_path): 183 | """Delete Object From JSON using json_path 184 | 185 | Arguments: 186 | - json_object: json as a dictionary object. 187 | - json_path: jsonpath expression 188 | 189 | Return new json_object 190 | 191 | Examples: 192 | | ${json_object}= | Delete Object From Json | ${json} | $..address.streetAddress | 193 | """ 194 | json_path_expr = self._parse(json_path) 195 | json_object_cpy = deepcopy(json_object) 196 | for match in reversed(json_path_expr.find(json_object_cpy)): 197 | path = match.path 198 | if isinstance(path, Index): 199 | del match.context.value[match.path.index] 200 | elif isinstance(path, Fields): 201 | del match.context.value[match.path.fields[0]] 202 | return json_object_cpy 203 | 204 | @staticmethod 205 | def convert_json_to_string(json_object, indent=None): 206 | """Convert JSON object to string 207 | 208 | Arguments: 209 | - json_object: json as a dictionary object. 210 | - indent: indent level for pretty-printing, see indent argument of python's [https://docs.python.org/3/library/json.html#json.dump|json.dump()] for details 211 | 212 | Return new json_string 213 | 214 | Examples: 215 | | ${json_str}= | Convert JSON To String | ${json_obj} | 216 | | ${json_str}= | Convert JSON To String | ${json_obj} | indent=${4} | 217 | """ 218 | return json.dumps(json_object, indent=indent) 219 | 220 | @staticmethod 221 | def convert_string_to_json(json_string): 222 | """Convert String to JSON object 223 | 224 | Arguments: 225 | - json_string: JSON string 226 | 227 | Return new json_object 228 | 229 | Examples: 230 | | ${json_object}= | Convert String to JSON | ${json_string} | 231 | """ 232 | return json.loads(json_string) 233 | 234 | def dump_json_to_file(self, dest_file, json_object, encoding=None): 235 | """Dump JSON to file 236 | 237 | Arguments: 238 | - dest_file: destination file 239 | - json_object: json as a dictionary object. 240 | 241 | Export the JSON object to a file 242 | 243 | Examples: 244 | | Dump JSON To File | ${OUTPUT_DIR)${/}output.json | ${json} | 245 | """ 246 | json_str = self.convert_json_to_string(json_object) 247 | with open(dest_file, "w", encoding=encoding) as json_file: 248 | json_file.write(json_str) 249 | return str(dest_file) 250 | 251 | def should_have_value_in_json(self, json_object, json_path): 252 | """Should Have Value In JSON using JSONPath 253 | 254 | Arguments: 255 | - json_object: json as a dictionary object. 256 | - json_path: jsonpath expression 257 | 258 | Fail if no value is found 259 | 260 | Examples: 261 | | Should Have Value In Json | ${json} | $..id_card_number | 262 | """ 263 | try: 264 | self.get_value_from_json(json_object, json_path, fail_on_empty=True) 265 | except AssertionError: 266 | fail(f"No value found for path {json_path}") 267 | 268 | def should_not_have_value_in_json(self, json_object, json_path): 269 | """Should Not Have Value In JSON using JSONPath 270 | 271 | Arguments: 272 | - json_object: json as a dictionary object. 273 | - json_path: jsonpath expression 274 | 275 | Fail if at least one value is found 276 | 277 | Examples: 278 | | Should Not Have Value In Json | ${json} | $..id_card_number | 279 | """ 280 | try: 281 | rv = self.get_value_from_json(json_object, json_path, fail_on_empty=True) 282 | except AssertionError: 283 | pass 284 | else: 285 | fail(f"Match found for parent {json_path}: {rv}") 286 | 287 | def validate_json_by_schema_file( 288 | self, json_object, path_to_schema, encoding=None 289 | ) -> None: 290 | """Validate json object by json schema file. 291 | Arguments: 292 | - json_object: json as a dictionary object. 293 | - json_path: path to file with json schema 294 | 295 | Fail if json object does not match the schema 296 | 297 | Examples: 298 | | Simple | Validate Json By Schema File | {"foo":bar} | ${CURDIR}${/}schema.json | 299 | """ 300 | with open(path_to_schema, encoding=encoding) as f: 301 | self.validate_json_by_schema(json_object, json.load(f)) 302 | 303 | @staticmethod 304 | def validate_json_by_schema(json_object, schema) -> None: 305 | """Validate json object by json schema. 306 | Arguments: 307 | - json_object: json as a dictionary object. 308 | - schema: schema as a dictionary object. 309 | 310 | Fail if json object does not match the schema 311 | 312 | Examples: 313 | | Simple | Validate Json By Schema | {"foo":bar} | {"$schema": "https://schema", "type": "object"} | 314 | """ 315 | try: 316 | jsonschema.validate(json_object, schema) 317 | except jsonschema.ValidationError as e: 318 | fail(f"{e.message}, Schema path: {' > '.join(e.schema_path)}") 319 | except jsonschema.SchemaError as e: 320 | fail(f"Json schema error: {e}") 321 | -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | include requirements.txt -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # robotframework-jsonlibrary 2 | ``JSONLibrary`` is a [Robot Framework](http://robotframework.org/) test library for manipulating [JSON](http://json.org/) Object. You can manipulate your JSON object using [JSONPath](http://goessner.net/articles/JsonPath/) 3 | 4 | JSONPath is an expression which can help to access to your JSON document. The JSONPath structure is in the same way as XPath which use for accessing XML document. This is an example of JSONPath syntax. 5 | 6 | | JSONPath | Description | 7 | |----------|-------------| 8 | | $ | the root object/element | 9 | | @ | the current object/element | 10 | | . or [] | child operator | 11 | | .. | recursive descent. JSONPath borrows this syntax from E4X | 12 | | * | wildcard. All objects/element regardless their names. | 13 | | [] | subscript operator. XPath uses it to iterate over element collections and for predicates. In Javascript and JSON it is the native array operator. | 14 | | [,] | Union operator in XPath results in a combination of node sets. JSONPath allows alternate names or array indices as a set. | 15 | | [start\: end\: step] | array slice operator borrowed from ES4 | 16 | | ?() | applies a filter (script) expression. | 17 | | () | script expression, using the underlying script engine. | 18 | 19 | This library can help you to add, get, update and delete your JSON object. So it's very useful in case that you have a very large JSON object. 20 | 21 | # Notes 22 | 23 | Please note this library is a bridge between the Robot Framework and the parser jsonpath-ng. Hence, issues related to parsing should be raised on https://github.com/h2non/jsonpath-ng 24 | 25 | Starting with version 0.4, Python2 support is dropped as Python2 reached end of life on 1st of January 2020. 26 | 27 | # Usage 28 | 29 | Install robotframework-jsonlibrary via ``pip`` command 30 | ```bash 31 | pip install -U robotframework-jsonlibrary 32 | ``` 33 | 34 | # Example Test Case 35 | 36 | |\*** Settings \***| | | | | 37 | |:----------------- |-------------------- |----------------- |----------- |----------------- | 38 | |Library | JSONLibrary | | | | 39 | |__\*** Test Cases \***__| | | | | 40 | |${json_obj}= | Load Json From File | example.json | | | 41 | |${object_to_add}= | Create Dictionary | country=Thailand | | | 42 | |${json_obj}= | Add Object To Json | ${json_obj} | $..address | ${object_to_add} | 43 | |${value}= | Get Value From Json | ${json_obj} | $..country | | 44 | |Should Be Equal As Strings | ${value[0]} | Thailand | | | 45 | |${value_to_update}=| Set Variable | Japan | | | 46 | |${json_obj}= | Update Value To Json | ${json_obj} | $..country | ${value_to_update}| 47 | |Should Be Equal As Strings | ${json_obj['country'] | ${value_to_update} | | | 48 | |Should Have Value In Json | ${json_obj} | $..isMarried | 49 | |Should Not Have Value In Json | ${json_obj} | $..hasSiblings | 50 | |Dump Json To File | \${OUTPUT_DIR}\${/}output.json | ${json} | 51 | |${schema_json_obj}= | Load Json From File | schema.json | | | 52 | |Validate Json By Schema | ${json_obj} | ${schema_json_obj} | | | 53 | |Validate Json By Schema File | ${json_obj} | schema.json | | | 54 | 55 | # Documentation 56 | For the detail keyword documentation. Go to this following link: 57 | 58 | https://robotframework-thailand.github.io/robotframework-jsonlibrary/ 59 | 60 | For an example of JSONPath expressions. Go to this link: 61 | 62 | https://goessner.net/articles/JsonPath/index.html#e3 63 | 64 | Parser: jsonpath-ng: 65 | 66 | https://github.com/h2non/jsonpath-ng 67 | 68 | This github: 69 | 70 | https://github.com/robotframework-thailand/robotframework-jsonlibrary 71 | 72 | #Help & Issues 73 | Mention me on Twitter [@nottyo](https://twitter.com/nottyo) 74 | 75 | 76 | 77 | -------------------------------------------------------------------------------- /UNLICENSE: -------------------------------------------------------------------------------- 1 | This is free and unencumbered software released into the public domain. 2 | 3 | Anyone is free to copy, modify, publish, use, compile, sell, or 4 | distribute this software, either in source code form or as a compiled 5 | binary, for any purpose, commercial or non-commercial, and by any 6 | means. 7 | 8 | In jurisdictions that recognize copyright laws, the author or authors 9 | of this software dedicate any and all copyright interest in the 10 | software to the public domain. We make this dedication for the benefit 11 | of the public at large and to the detriment of our heirs and 12 | successors. We intend this dedication to be an overt act of 13 | relinquishment in perpetuity of all present and future rights to this 14 | software under copyright law. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 19 | IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR 20 | OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, 21 | ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 22 | OTHER DEALINGS IN THE SOFTWARE. 23 | 24 | For more information, please refer to 25 | -------------------------------------------------------------------------------- /acceptance/JSONLibrary.robot: -------------------------------------------------------------------------------- 1 | *** Settings *** 2 | Library JSONLibrary 3 | Library Collections 4 | Library String 5 | Library OperatingSystem 6 | Test Setup SetUp Test 7 | Default Tags JSONLibrary 8 | 9 | *** Keywords *** 10 | SetUp Test 11 | ${json}= Load Json From File ${CURDIR}${/}..${/}tests${/}json${/}example.json 12 | Set Test Variable ${json_obj_input} ${json} 13 | Set Test Variable ${json_obj_orignal} ${json} 14 | 15 | *** Test Cases *** 16 | TestAddJSONObjectByJSONPath 17 | [Documentation] Adding some json object using JSONPath 18 | ${object_to_add}= Create Dictionary latitude=13.1234 longitude=130.1234 19 | ${json_obj1}= Add Object To Json ${json_obj_input} $..address ${object_to_add} 20 | Dictionary Should Contain Sub Dictionary ${json_obj1['address']} ${object_to_add} 21 | ${json_obj2}= Add Object To Json ${json_obj1} $.friends ${None} 22 | Dictionary Should Not Contain Key ${json_obj1} friends 23 | Dictionary Should Contain Key ${json_obj2} friends 24 | Dictionaries Should Be Equal ${json_obj_orignal} ${json_obj_input} 25 | 26 | TestGetValueByJSONPath 27 | [Documentation] Get some json object using JSONPath 28 | ${values}= Get Value From Json ${json_obj_input} $..address.postalCode 29 | Should Be Equal As Strings ${values[0]} 630-0192 30 | 31 | ${values}= Get Value From Json ${json_obj_input} $..errorField 32 | ${size}= Get Length ${values} 33 | Should Be Equal As Integers ${size} 0 34 | 35 | TestErrorGetValueByJSONPath 36 | [Documentation] Check Get Value From Json can fail if no match is found 37 | Run Keyword And Expect Error *failed to find* Get Value From Json ${json_obj_input} $..errorField fail_on_empty=${True} 38 | 39 | TestUpdateValueByJSONPath 40 | [Documentation] Update value to json object using JSONPath 41 | ${json_obj}= Update Value To Json ${json_obj_input} $..address.city Bangkok 42 | ${updated_city}= Get Value From Json ${json_obj} $..address.city 43 | Should Be Equal As Strings ${updated_city[0]} Bangkok 44 | Dictionaries Should Be Equal ${json_obj_orignal} ${json_obj_input} 45 | 46 | TestShouldHaveValueByJSONPath 47 | [Documentation] Check a value can be found in json object using JSONPath 48 | Should Have Value In Json ${json_obj_input} $..isMarried 49 | 50 | Run Keyword And Expect Error *No value found* Should Have Value In Json ${json_obj_input} $..hasSiblings 51 | 52 | TestShouldNotHaveValueByJSONPath 53 | [Documentation] Check a value cannot be found in json object using JSONPath 54 | Should Not Have Value In Json ${json_obj_input} $..hasSiblings 55 | 56 | Run Keyword And Expect Error *Match found* Should Not Have Value In Json ${json_obj_input} $..isMarried 57 | 58 | TestInvalidSyntaxByJSONPath 59 | [Documentation] Check that an invalid syntax fail the test and doesn't crash Robot 60 | ${value}= Get Value From Json ${json_obj_input} $.bankAccounts[?(@.amount>=100)].bank 61 | Should Be Equal As Strings "${value}" "['WesternUnion', 'HSBC']" 62 | Run Keyword And Expect Error Parser failed to understand syntax * 63 | ... Get Value From Json ${json_obj_input} $.bankAccounts[?(@.amount=>100)].bank 64 | 65 | TestDeleteObjectByJSONPath 66 | [Documentation] Delete object from json object using JSONPath 67 | ${json_obj}= Delete Object From Json ${json_obj_input} $..isMarried 68 | Dictionary Should Not Contain Key ${json_obj} isMarried 69 | Dictionaries Should Be Equal ${json_obj_orignal} ${json_obj_input} 70 | 71 | TestDeleteArrayElementsByJSONPath 72 | [Documentation] Delete array elements from json object using JSONPath 73 | ${json_obj1}= Delete Object From Json ${json_obj_input} $..phoneNumbers[0] 74 | Length Should Be ${json_obj1['phoneNumbers']} 2 75 | ${json_obj2}= Delete Object From Json ${json_obj1} $..phoneNumbers[*] 76 | Length Should Be ${json_obj1['phoneNumbers']} 2 77 | Length Should Be ${json_obj2['phoneNumbers']} 0 78 | Dictionaries Should Be Equal ${json_obj_orignal} ${json_obj_input} 79 | 80 | TestConvertJSONToString 81 | [Documentation] Convert Json To String 82 | ${json_str}= Convert Json To String ${json_obj_input} 83 | Should Be String ${json_str} 84 | 85 | TestDumpJSONToFile 86 | [Documentation] Dumps Json to file 87 | Dump Json to file ${TEMPDIR}/sample_dump.json ${json_obj_input} 88 | File Should Exist ${TEMPDIR}/sample_dump.json 89 | 90 | TestValidateJsonBySchemaFile 91 | [Documentation] Validate Json by schema file 92 | Validate Json By Schema File ${json_obj_input} ${CURDIR}${/}..${/}tests${/}json${/}example_schema.json 93 | 94 | TestValidateJsonBySchema 95 | [Documentation] Validate Json by schema 96 | ${schema} Load Json From File ${CURDIR}${/}..${/}tests${/}json${/}example_schema.json 97 | Validate Json By Schema ${json_obj_input} ${schema} 98 | 99 | TestValidateJsonBySchemaFileFail 100 | [Documentation] Validate Json by schema file and fail 101 | ${new_json} Delete Object From Json ${json_obj_input} $..phoneNumbers 102 | Run Keyword And Expect Error * is a required property, Schema path: * 103 | ... Validate Json By Schema File ${new_json} ${CURDIR}${/}..${/}tests${/}json${/}example_schema.json 104 | 105 | TestValidateJsonBySchemaFail 106 | [Documentation] Validate Json by schema and fail 107 | ${schema} Load Json From File ${CURDIR}${/}..${/}tests${/}json${/}example_schema.json 108 | ${new_json} Delete Object From Json ${json_obj_input} $..phoneNumbers 109 | Run Keyword And Expect Error * is a required property, Schema path: * 110 | ... Validate Json By Schema ${new_json} ${schema} 111 | 112 | TestValidateJsonByInvalidSchemaFile 113 | [Documentation] Validate Json by invalid schema file 114 | Run Keyword And Expect Error Json schema error: * 115 | ... Validate Json By Schema File ${json_obj_input} ${CURDIR}${/}..${/}tests${/}json${/}broken_schema.json 116 | 117 | TestValidateJsonByInvalidSchema 118 | [Documentation] Validate Json by invalid schema 119 | ${schema} Load Json From File ${CURDIR}${/}..${/}tests${/}json${/}broken_schema.json 120 | Run Keyword And Expect Error Json schema error: * 121 | ... Validate Json By Schema ${json_obj_input} ${schema} 122 | -------------------------------------------------------------------------------- /docs/JSONLibrary.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 576 | 733 | 746 | 769 | 841 | 872 | 1075 | 1079 | 1091 | 1104 | 1107 | 1108 | 1109 | 1110 | 1111 |
1112 |

Opening library documentation failed

1113 | 1118 |
1119 | 1120 | 1127 | 1128 | 1390 | 1391 | 1445 | 1446 | 1474 | 1475 | 1494 | 1495 | 1517 | 1518 | 1519 | 1526 | 1527 | 1542 | 1543 | 1584 | 1585 | 1608 | 1609 | 1610 | 1620 | 1621 | 1679 | 1680 | 1685 | 1686 | 1687 | 1688 | -------------------------------------------------------------------------------- /docs/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Redirect Page... 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /requirements-dev.txt: -------------------------------------------------------------------------------- 1 | twine 2 | wheel 3 | pylint 4 | invoke 5 | black 6 | tox 7 | pytest 8 | pytest-cov 9 | flake8 10 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | robotframework>=3.0 2 | jsonpath-ng>=1.4.3 3 | jsonschema>=2.5.1 4 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | import os 2 | from setuptools import setup 3 | 4 | HERE = os.path.abspath(os.path.dirname(__file__)) 5 | version = {} 6 | with open(os.path.join(HERE, "JSONLibrary", "__version__.py"), encoding="utf8") as f: 7 | exec(f.read(), version) 8 | 9 | requirements = [ 10 | i.strip() for i in open("requirements.txt", encoding="utf8").readlines() 11 | ] 12 | 13 | setup( 14 | name="robotframework-jsonlibrary", 15 | version=version["__version__"], 16 | description="robotframework-jsonlibrary is a Robot Framework " 17 | "test library for manipulating JSON Object. " 18 | "You can manipulate your JSON object using JSONPath", 19 | author="Traitanit Huangsri", 20 | author_email="traitanit.hua@gmail.com", 21 | url="https://github.com/nottyo/robotframework-jsonlibrary.git", 22 | packages=["JSONLibrary"], 23 | package_dir={"robotframework-jsonlibrary": "JSONLibrary"}, 24 | install_requires=requirements, 25 | include_package_data=True, 26 | keywords="testing robotframework json jsonschema jsonpath", 27 | classifiers=[ 28 | "Development Status :: 5 - Production/Stable", 29 | "Intended Audience :: Developers", 30 | "Topic :: Software Development :: Testing", 31 | "License :: Public Domain", 32 | "Programming Language :: Python :: 3", 33 | "Framework :: Robot Framework :: Library", 34 | ], 35 | ) 36 | -------------------------------------------------------------------------------- /tasks.py: -------------------------------------------------------------------------------- 1 | import os 2 | import sys 3 | import shutil 4 | from invoke import task 5 | 6 | 7 | def _get_wheel_file() -> str: 8 | dist = "dist" 9 | assert len(os.listdir(dist)) > 0, "No files found in dist folder" 10 | wheel_file = os.path.join(dist, os.listdir(dist)[0]) 11 | return wheel_file 12 | 13 | 14 | @task 15 | def clean(_): 16 | shutil.rmtree( 17 | os.path.join("tests", "__out__"), 18 | ignore_errors=True, 19 | ) 20 | shutil.rmtree(".tox", ignore_errors=True) 21 | shutil.rmtree(".pytest_cache", ignore_errors=True) 22 | shutil.rmtree("build", ignore_errors=True) 23 | shutil.rmtree("dist", ignore_errors=True) 24 | shutil.rmtree("robotframework_jsonlibrary.egg-info", ignore_errors=True) 25 | for path, _, _ in os.walk("."): 26 | if path.endswith("__pycache__"): 27 | shutil.rmtree(path, ignore_errors=True) 28 | 29 | 30 | @task(clean) 31 | def build(ctx): 32 | ctx.run(f"{sys.executable} setup.py bdist_wheel", hide="both") 33 | wheel_file = _get_wheel_file() 34 | assert wheel_file.endswith(".whl") 35 | 36 | 37 | @task 38 | def uninstall(ctx): 39 | ctx.run( 40 | f"{sys.executable} -m pip uninstall robotframework-jsonlibrary -y", hide="both" 41 | ) 42 | 43 | 44 | @task(build, uninstall) 45 | def install(ctx): 46 | wheel_file = _get_wheel_file() 47 | ctx.run(f"{sys.executable} -m pip install {wheel_file}", hide="both") 48 | 49 | 50 | @task(install) 51 | def test(ctx): 52 | ctx.run("tox") 53 | 54 | 55 | @task 56 | def style_check(ctx): 57 | ctx.run("black . --check --diff") 58 | 59 | 60 | @task 61 | def reformat_code(ctx): 62 | ctx.run("black .") 63 | 64 | 65 | @task 66 | def publish(ctx): 67 | ctx.run(f"{sys.executable} -m twine upload dist/*") 68 | 69 | 70 | @task 71 | def docs(ctx): 72 | ctx.run(f"{sys.executable} -m robot.libdoc JSONLibrary docs/JSONLibrary.html") 73 | 74 | 75 | @task(install) 76 | def lint(ctx): 77 | ctx.run("pylint JSONLibrary --disable=R,C,W0703,W0212,W1203") 78 | uninstall(ctx) 79 | clean(ctx) 80 | -------------------------------------------------------------------------------- /tests/.coveragerc: -------------------------------------------------------------------------------- 1 | [run] 2 | include = JSONLibrary/* 3 | 4 | [xml] 5 | output = tests/__out__/coverage/xml/coverage.xml 6 | 7 | [html] 8 | directory = tests/__out__/coverage/html -------------------------------------------------------------------------------- /tests/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | -------------------------------------------------------------------------------- /tests/json/broken_schema.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schemas": "https://json-schema.org/draft/2020-12/schema", 3 | "type": "objects" 4 | } -------------------------------------------------------------------------------- /tests/json/example.json: -------------------------------------------------------------------------------- 1 | { 2 | "firstName": "John", 3 | "lastName": "doe", 4 | "age": 26, 5 | "gender": "male", 6 | "favoriteColor": [ 7 | "blue" 8 | ], 9 | "isMarried": false, 10 | "address": { 11 | "streetAddress": "naist street", 12 | "city": "Nara", 13 | "postalCode": "630-0192" 14 | }, 15 | "phoneNumbers": [ 16 | { 17 | "type": "iPhone", 18 | "number": "0123-4567-8888" 19 | }, { 20 | "type": "home", 21 | "number": "0123-4567-8910" 22 | }, { 23 | "type": "car", 24 | "number": "0123-4567-8999" 25 | } 26 | ], 27 | "bankAccounts": [ 28 | { 29 | "bank": "WesternUnion", 30 | "amount": "503" 31 | }, { 32 | "bank": "HSBC", 33 | "amount": "1320" 34 | }, { 35 | "bank": "GoldenSack", 36 | "amount": "-10" 37 | } 38 | ], 39 | "siblings": [], 40 | "occupation": null 41 | } -------------------------------------------------------------------------------- /tests/json/example_schema.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "object", 3 | "properties": { 4 | "firstName": { 5 | "type": "string" 6 | }, 7 | "lastName": { 8 | "type": "string" 9 | }, 10 | "age": { 11 | "type": "integer" 12 | }, 13 | "gender": { 14 | "type": "string" 15 | }, 16 | "favoriteColor": { 17 | "type": "array", 18 | "items": { 19 | "type": "string" 20 | } 21 | }, 22 | "isMarried": { 23 | "type": "boolean" 24 | }, 25 | "address": { 26 | "type": "object", 27 | "properties": { 28 | "streetAddress": { 29 | "type": "string" 30 | }, 31 | "city": { 32 | "type": "string" 33 | }, 34 | "postalCode": { 35 | "type": "string" 36 | } 37 | } 38 | }, 39 | "phoneNumbers": { 40 | "type": "array", 41 | "items": { 42 | "type": "object", 43 | "properties": { 44 | "type": { 45 | "type": "string" 46 | }, 47 | "number": { 48 | "type": "string" 49 | } 50 | } 51 | } 52 | }, 53 | "bankAccounts": { 54 | "type": "array", 55 | "items": { 56 | "type": "object", 57 | "properties": { 58 | "bank": { 59 | "type": "string" 60 | }, 61 | "amount": { 62 | "type": "string" 63 | } 64 | } 65 | } 66 | }, 67 | "siblings": { 68 | "type": "array" 69 | }, 70 | "occupation": { 71 | "type": "null" 72 | } 73 | }, 74 | "required":["firstName", "lastName", "age", "gender", "favoriteColor", "isMarried", "address", "phoneNumbers", "bankAccounts"] 75 | } -------------------------------------------------------------------------------- /tests/test_JSONLibrary.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | __author__ = "Traitanit Huangsri" 4 | __email__ = "traitanit.hua@ascendcorp.com" 5 | 6 | import os 7 | import tempfile 8 | import pytest 9 | from copy import deepcopy 10 | from JSONLibrary import JSONLibrary 11 | 12 | 13 | class TestJSONLibrary: 14 | json_library = JSONLibrary() 15 | dir_path = os.path.dirname(os.path.realpath(__file__)) 16 | 17 | @pytest.fixture(autouse=True) 18 | def json(self): 19 | return self.json_library.load_json_from_file( 20 | os.path.join(self.dir_path, "json", "example.json") 21 | ) 22 | 23 | def test_add_dict_element_to_json(self, json): 24 | json_path = "$..address" 25 | data_to_add = {"latitude": "13.1234", "longitude": "130.1234"} 26 | json_cpy = deepcopy(json) 27 | json_object = self.json_library.add_object_to_json( 28 | json_cpy, json_path, data_to_add 29 | ) 30 | assert json_object["address"] == {**json_object["address"], **data_to_add} 31 | assert json_cpy == json 32 | 33 | def test_add_new_object_to_root(self, json): 34 | json_path = "$.country" 35 | data_to_add = "Thailand" 36 | json_cpy = deepcopy(json) 37 | json_object = self.json_library.add_object_to_json( 38 | json_cpy, json_path, data_to_add 39 | ) 40 | assert json_object["country"] == "Thailand" 41 | assert json_cpy == json 42 | 43 | def test_add_list_element_to_json(self, json): 44 | json_path = "$..favoriteColor" 45 | data_to_add = "green" 46 | json_cpy = deepcopy(json) 47 | json_object = self.json_library.add_object_to_json( 48 | json_cpy, json_path, data_to_add 49 | ) 50 | assert data_to_add in json_object["favoriteColor"] 51 | assert json_cpy, json 52 | 53 | def test_get_value_from_json_path(self, json): 54 | json_path = "$..number" 55 | values = self.json_library.get_value_from_json(json, json_path) 56 | expected_result = ["0123-4567-8888", "0123-4567-8910", "0123-4567-8999"] 57 | assert values == expected_result 58 | 59 | def test_get_none_from_json_path(self, json): 60 | json_path = "$..occupation" 61 | values = self.json_library.get_value_from_json(json, json_path) 62 | assert len(values) > 0 63 | for v in values: 64 | assert v is None 65 | 66 | def test_get_empty_list_from_json_path(self, json): 67 | json_path = "$..siblings" 68 | values = self.json_library.get_value_from_json(json, json_path) 69 | expected_result = [] 70 | assert values, expected_result 71 | 72 | def test_get_value_from_json_path_not_found(self, json): 73 | json_path = "$..notfound" 74 | with pytest.raises(AssertionError): 75 | self.json_library.get_value_from_json(json, json_path, fail_on_empty=True) 76 | 77 | # backward-compatibility, fail_on_empty is False by default 78 | values = self.json_library.get_value_from_json(json, json_path) 79 | expected_result = [] 80 | assert values == expected_result 81 | 82 | def test_has_value_from_json_path_passed(self, json): 83 | json_path = "$..isMarried" 84 | self.json_library.should_have_value_in_json(json, json_path) 85 | 86 | def test_has_value_from_json_path_failed(self, json): 87 | json_path = "$..hasSiblings" 88 | with pytest.raises(AssertionError): 89 | self.json_library.should_have_value_in_json(json, json_path) 90 | 91 | def test_has_no_value_from_json_path_passed(self, json): 92 | json_path = "$..hasSiblings" 93 | self.json_library.should_not_have_value_in_json(json, json_path) 94 | 95 | def test_has_no_value_from_json_path_failed(self, json): 96 | json_path = "$..isMarried" 97 | with pytest.raises(AssertionError): 98 | self.json_library.should_not_have_value_in_json(json, json_path) 99 | 100 | def test_update_value_to_json(self, json): 101 | json_path = "$..address.streetAddress" 102 | value_to_update = "Ratchadapisek Road" 103 | json_cpy = deepcopy(json) 104 | json_object = self.json_library.update_value_to_json( 105 | json_cpy, json_path, value_to_update 106 | ) 107 | assert json_cpy == json 108 | assert value_to_update == json_object["address"]["streetAddress"] 109 | 110 | def test_update_value_to_json_as_index(self, json): 111 | json_path = "$..phoneNumbers[0].type" 112 | value_to_update = "mobile" 113 | json_cpy = deepcopy(json) 114 | json_object = self.json_library.update_value_to_json( 115 | json_cpy, json_path, value_to_update 116 | ) 117 | assert json_cpy == json 118 | assert value_to_update == json_object["phoneNumbers"][0]["type"] 119 | 120 | def test_delete_object_from_json(self, json): 121 | json_path = "$..isMarried" 122 | json_cpy = deepcopy(json) 123 | json_object = self.json_library.delete_object_from_json(json_cpy, json_path) 124 | assert "isMarried" not in json_object 125 | assert json_cpy == json 126 | 127 | def test_delete_array_elements_from_json(self, json): 128 | json_path = "$..phoneNumbers[0]" 129 | json_cpy = deepcopy(json) 130 | json_object = self.json_library.delete_object_from_json(json_cpy, json_path) 131 | assert not any(pn["type"] == "iPhone" for pn in json_object["phoneNumbers"]) 132 | assert json_cpy == json 133 | 134 | def test_delete_all_array_elements_from_json(self, json): 135 | json_path = "$..phoneNumbers[*]" 136 | json_cpy = deepcopy(json) 137 | json_object = self.json_library.delete_object_from_json(json_cpy, json_path) 138 | expected_result = [] 139 | assert expected_result == json_object["phoneNumbers"] 140 | assert json_cpy, json 141 | 142 | def test_invalid_syntax_doesnt_crash(self, json): 143 | json_path = "$.bankAccounts[?(@.amount>=100)].bank" 144 | values = self.json_library.get_value_from_json(json, json_path) 145 | expected_result = ["WesternUnion", "HSBC"] 146 | assert values == expected_result 147 | 148 | json_path = "$.bankAccounts[?(@.amount=>100)].bank" 149 | with pytest.raises(AssertionError): 150 | self.json_library.get_value_from_json(json, json_path) 151 | 152 | def test_convert_json_to_string(self, json): 153 | json_str = self.json_library.convert_json_to_string(json) 154 | assert isinstance(json_str, str) 155 | 156 | def test_convert_json_to_string_with_indent(self, json): 157 | json_str = self.json_library.convert_json_to_string(json, indent=4) 158 | assert len(json_str_as_list := json_str.split("\n")) == 45 159 | assert json_str_as_list[1][:4] == " " * 4 160 | 161 | def test_convert_string_to_json(self, json): 162 | json_obj = self.json_library.convert_string_to_json('{"firstName": "John"}') 163 | assert "firstName" in json_obj 164 | 165 | def test_dump_json_to_file(self, json): 166 | with tempfile.TemporaryDirectory() as temp_dir: 167 | file_path = "%ssample.json" % temp_dir 168 | json_file = self.json_library.dump_json_to_file(file_path, json) 169 | assert os.path.exists(json_file) 170 | 171 | def test_validate_json_by_schema_file(self, json): 172 | schema_path = os.path.join(self.dir_path, "json", "example_schema.json") 173 | self.json_library.validate_json_by_schema_file(json, schema_path) 174 | 175 | def test_validate_json_by_schema(self, json): 176 | self.json_library.validate_json_by_schema( 177 | json, {"type": "object", "properties": {"firstName": {"type": "string"}}} 178 | ) 179 | 180 | def test_validate_json_by_schema_file_fail(self, json): 181 | schema_path = os.path.join(self.dir_path, "json", "example_schema.json") 182 | new_json = self.json_library.delete_object_from_json(json, "$..phoneNumbers") 183 | with pytest.raises(AssertionError): 184 | self.json_library.validate_json_by_schema_file(new_json, schema_path) 185 | 186 | def test_validate_json_by_schema_fail(self, json): 187 | with pytest.raises(AssertionError): 188 | self.json_library.validate_json_by_schema(json, {"type": "array"}) 189 | 190 | def test_validate_json_by_invalid_schema(self, json): 191 | with pytest.raises(AssertionError): 192 | self.json_library.validate_json_by_schema(json, "foo") 193 | 194 | def test_validate_json_by_invalid_schema_file(self, json): 195 | schema_path = os.path.join(self.dir_path, "json", "broken_schema.json") 196 | with pytest.raises(AssertionError): 197 | self.json_library.validate_json_by_schema_file(json, schema_path) 198 | -------------------------------------------------------------------------------- /tox.ini: -------------------------------------------------------------------------------- 1 | [tox] 2 | envlist = py38,py39 3 | 4 | [testenv] 5 | setenv = 6 | COVERAGE_FILE = {env:COVERAGE_FILE:{envdir}/tmp/coverage/.coverage} 7 | deps = 8 | pytest 9 | pytest-cov 10 | coverage 11 | robotframework 12 | commands = 13 | pytest --cov-config=tests/.coveragerc --cov --cov-report term tests/ 14 | coverage xml --rcfile tests/.coveragerc 15 | coverage html --rcfile tests/.coveragerc 16 | robot -d tests/__out__/robot acceptance/ 17 | --------------------------------------------------------------------------------