├── .github └── workflows │ └── qapir-demo.yml ├── .qapir ├── environments │ └── staging.yml ├── suites │ └── test_suite.yml └── tests │ ├── graphql_test.yml │ ├── headers_test.yml │ ├── http_status_test.yml │ ├── jsonpath_test.yml │ ├── multi_step_test.yml │ ├── optional_fields_test.yml │ └── type_tests │ ├── arrays_test.yml │ ├── bools_test.yml │ ├── numbers_test.yml │ ├── strings_test.yml │ └── types_test.yml ├── EULA.txt └── README.md /.github/workflows/qapir-demo.yml: -------------------------------------------------------------------------------- 1 | name: qapir-demo 2 | 3 | on: 4 | workflow_dispatch: 5 | 6 | jobs: 7 | 8 | run-tests: 9 | runs-on: ubuntu-latest 10 | steps: 11 | - name: Checkout 12 | uses: actions/checkout@v2 13 | 14 | - name: Get qapir 15 | run: | 16 | wget https://github.com/vrtxlabs/qapir/releases/latest/download/qapir-linux-amd64.tgz 17 | sudo tar -zxvf qapir-linux-amd64.tgz -C /opt/ 18 | sudo ln -s /opt/qapir /usr/bin/qapir 19 | rm -rf qapir-linux-amd64.tgz 20 | 21 | - name: Start demo-app and run tests 22 | run: | 23 | cd .qapir 24 | qapir demo-app > /dev/null 2>&1 & 25 | sleep 5 26 | qapir run suite -f test_suite.yml -e staging.yml 27 | 28 | - name: Upload results 29 | if: ${{ always() }} 30 | uses: actions/upload-artifact@v4 31 | with: 32 | name: test-report 33 | path: /home/runner/work/qapir/qapir/.qapir/reports/*.html 34 | -------------------------------------------------------------------------------- /.qapir/environments/staging.yml: -------------------------------------------------------------------------------- 1 | environment_variables: 2 | url: "http://127.0.0.1:7070" 3 | -------------------------------------------------------------------------------- /.qapir/suites/test_suite.yml: -------------------------------------------------------------------------------- 1 | test_files: 2 | - type_tests/types_test.yml 3 | - type_tests/numbers_test.yml 4 | - type_tests/strings_test.yml 5 | - type_tests/bools_test.yml 6 | - type_tests/arrays_test.yml 7 | - optional_fields_test.yml 8 | - jsonpath_test.yml 9 | - headers_test.yml 10 | - http_status_test.yml 11 | - multi_step_test.yml 12 | - graphql_test.yml 13 | -------------------------------------------------------------------------------- /.qapir/tests/graphql_test.yml: -------------------------------------------------------------------------------- 1 | name: Graphql test with parameters '${param.code}' and '${param.name}' 2 | 3 | test_steps: 4 | 5 | - description: Send a graphql request 6 | type: graphql 7 | url: https://countries.trevorblades.com/graphql 8 | method: POST 9 | expected_http_status: 200 10 | 11 | graphql_query: | 12 | query Query($code: ID!) { 13 | country(code: $code) { 14 | name 15 | } 16 | } 17 | 18 | graphql_variables: 19 | code: ${param.code} 20 | 21 | graphql_operation_name: "Query" 22 | 23 | graphql_extensions: 24 | customKey: "customValue" 25 | 26 | execute: | 27 | FROM 28 | ${response.body} 29 | SELECT 30 | $.data.country.name AS ${var.country_name} 31 | EXPECT 32 | ${var.country_name} -type string AND 33 | ${var.country_name} -eq "${param.name}"; 34 | 35 | test_parameters: 36 | code: 37 | - AD 38 | - DE 39 | name: 40 | - Andorra 41 | - Germany -------------------------------------------------------------------------------- /.qapir/tests/headers_test.yml: -------------------------------------------------------------------------------- 1 | name: Find a response-header and check its type 2 | 3 | test_steps: 4 | - description: GET /types 5 | type: http 6 | url: ${env.url}/types 7 | method: GET 8 | expected_http_status: 200 9 | execute: | 10 | SELECT 11 | Qapir-Header AS ${var.header} 12 | FROM 13 | ${response.headers} 14 | EXPECT 15 | ${var.header} -contains "-" AND 16 | ${var.header} -type string; 17 | -------------------------------------------------------------------------------- /.qapir/tests/http_status_test.yml: -------------------------------------------------------------------------------- 1 | name: Check that http-status ${param.status} is received and evaluated 2 | 3 | test_steps: 4 | - description: GET /status ${param.status} 5 | type: http 6 | url: ${env.url}/status 7 | query_params: 8 | value: ${param.status} 9 | method: GET 10 | expected_http_status: ${param.status} 11 | 12 | test_parameters: 13 | status: 14 | - 200 15 | - 302 16 | - 404 17 | - 503 18 | -------------------------------------------------------------------------------- /.qapir/tests/jsonpath_test.yml: -------------------------------------------------------------------------------- 1 | name: Demonstrate multiple types of supported jsonpath statements 2 | 3 | test_steps: 4 | - description: GET /types 5 | type: http 6 | url: ${env.url}/types 7 | method: GET 8 | expected_http_status: 200 9 | execute: | 10 | SELECT 11 | $.data.object_array[?(@.number_field>456)] AS ${var.gt_456} AND 12 | $.data.object_array[?(@.number_field==456)].string_field AS ${var.eq_456_string} AND 13 | $.data.number_array[?(@!=15)] AS ${var.non_15} 14 | FROM 15 | ${response.body} 16 | EXPECT 17 | ${var.eq_456_string} -eq string_value_1; 18 | 19 | SELECT 20 | $.number_field AS ${var.filtered_number_field} AND 21 | $.string_field AS ${var.filtered_string_field} 22 | FROM 23 | ${var.gt_456} 24 | EXPECT 25 | ${var.filtered_number_field} -eq 789 AND 26 | ${var.filtered_string_field} -eq "string_value_2"; 27 | 28 | SELECT_ALL 29 | $ AS ${var.filtered_number} 30 | FROM 31 | ${var.non_15} 32 | EXPECT_ALL 33 | ${var.filtered_number} -neq 15 AND 34 | ${var.filtered_number} -in [4, 8, 16, 23, 42]; 35 | -------------------------------------------------------------------------------- /.qapir/tests/multi_step_test.yml: -------------------------------------------------------------------------------- 1 | name: Demonstrate a multi-step test that runs with parameter '${param.name}' 2 | 3 | test_parameters: 4 | name: 5 | - some_job_name 6 | - ${gen.timestamp} 7 | 8 | test_steps: 9 | - description: Start a new job 10 | type: http 11 | url: ${env.url}/jobs 12 | method: POST 13 | expected_http_status: 200 14 | payload: | 15 | { 16 | "name": "${param.name}" 17 | } 18 | execute: | 19 | SELECT 20 | $.data.name AS ${var.job_name} AND 21 | $.data.status AS ${var.job_status} 22 | FROM 23 | ${response.body} 24 | EXPECT 25 | ${var.job_name} -eq ${param.name} AND 26 | ${var.job_status} -eq "in_progress"; 27 | 28 | - description: Wait until job finishes 29 | type: http 30 | url: ${env.url}/jobs 31 | method: GET 32 | query_params: 33 | name: ${param.name} 34 | expected_http_status: 200 35 | retries: 2 36 | interval: 5s 37 | execute: | 38 | FROM 39 | ${response.body} 40 | SELECT 41 | $.data[0].name AS ${var.started_job_name} AND 42 | $.data[0].status AS ${var.started_job_status} 43 | EXPECT 44 | ${var.started_job_name} -eq ${param.name} AND 45 | ${var.started_job_status} -eq "done"; 46 | 47 | - description: Delete completed job 48 | type: http 49 | method: DELETE 50 | url: ${env.url}/jobs 51 | query_params: 52 | name: ${var.job_name} 53 | expected_http_status: 204 54 | -------------------------------------------------------------------------------- /.qapir/tests/optional_fields_test.yml: -------------------------------------------------------------------------------- 1 | name: Showcase validation of fields that can be missing 2 | 3 | test_steps: 4 | - description: GET /types 5 | type: http 6 | url: ${env.url}/types 7 | method: GET 8 | expected_http_status: 200 9 | execute: | 10 | SELECT 11 | $.data.object_array AS ${var.object_array} 12 | FROM 13 | ${response.body}; 14 | 15 | SELECT_ALL 16 | $.number_field AS ${var.iterable_number_field} AND 17 | FROM 18 | ${var.object_array} 19 | EXPECT_ALL 20 | IF_EXISTS ${var.iterable_number_field} -in [456, 789] 21 | EXPECT 22 | ${var.iterable_number_field#0} -eq 456 AND 23 | ${var.iterable_number_field#1} -eq 789 AND 24 | IF_EXISTS ${var.iterable_number_field#2} -neq 123; 25 | -------------------------------------------------------------------------------- /.qapir/tests/type_tests/arrays_test.yml: -------------------------------------------------------------------------------- 1 | name: Demonstrate all types of validation available for arrays 2 | 3 | test_steps: 4 | - description: GET /types 5 | type: http 6 | url: ${env.url}/types 7 | method: GET 8 | expected_http_status: 200 9 | execute: | 10 | SELECT 11 | $.data.string_array AS ${var.string_array} AND 12 | $.data.number_array AS ${var.number_array} AND 13 | $.data.object_array AS ${var.object_array} 14 | FROM 15 | ${response.body} 16 | EXPECT 17 | ${var.string_array} -len 2 AND 18 | ${var.number_array} -len <10 AND 19 | ${var.object_array} -len >1 AND 20 | ${var.number_array} -contains 23 AND 21 | ${var.string_array} -ncontains "unexpected"; 22 | 23 | SELECT_ALL 24 | $.number_field AS ${var.iterable_number_field} AND 25 | $.string_field AS ${var.iterable_string_field} 26 | FROM 27 | ${var.object_array} 28 | EXPECT_ALL 29 | ${var.iterable_string_field} -contains "string_value" 30 | EXPECT 31 | ${var.iterable_number_field#0} -eq 456 AND 32 | ${var.iterable_string_field#0} -eq "string_value_1" AND 33 | ${var.iterable_number_field#1} -eq 789 AND 34 | ${var.iterable_string_field#1} -eq "string_value_2"; 35 | 36 | -------------------------------------------------------------------------------- /.qapir/tests/type_tests/bools_test.yml: -------------------------------------------------------------------------------- 1 | name: Demonstrate all types of validation available for bool 2 | 3 | test_steps: 4 | - description: GET /types 5 | type: http 6 | url: ${env.url}/types 7 | method: GET 8 | expected_http_status: 200 9 | execute: | 10 | SELECT 11 | $.data.bool AS ${var.bool} 12 | FROM 13 | ${response.body} 14 | EXPECT 15 | ${var.bool} -eq true; 16 | -------------------------------------------------------------------------------- /.qapir/tests/type_tests/numbers_test.yml: -------------------------------------------------------------------------------- 1 | name: Demonstrate all types of validation available for numbers 2 | 3 | test_steps: 4 | - description: GET /types 5 | type: http 6 | url: ${env.url}/types 7 | method: GET 8 | expected_http_status: 200 9 | execute: | 10 | SELECT 11 | $.data.number AS ${var.number} AND 12 | $.data.number_array AS ${var.number_array} AND 13 | $.data.number_array[0] AS ${var.number_array_elem} 14 | FROM 15 | ${response.body} 16 | EXPECT 17 | ${var.number} -exists AND 18 | ${var.number} -eq 3.14 AND 19 | ${var.number} -gt 3.13 AND 20 | ${var.number} -lt 3.15 AND 21 | ${var.number} -nin ${var.number_array} AND 22 | ${var.number_array} -contains 23 AND 23 | ${var.number_array_elem} -neq 789; -------------------------------------------------------------------------------- /.qapir/tests/type_tests/strings_test.yml: -------------------------------------------------------------------------------- 1 | name: Demonstrate all types of validation available for strings 2 | 3 | test_steps: 4 | - description: GET /types 5 | type: http 6 | url: ${env.url}/types 7 | method: GET 8 | expected_http_status: 200 9 | execute: | 10 | SELECT 11 | $.data.string AS ${var.string} AND 12 | $.data.string_array[0] AS ${var.string_array_elem_1} AND 13 | $.data.string_array[1] AS ${var.string_array_elem_2} 14 | FROM 15 | ${response.body} 16 | EXPECT 17 | ${var.string} -exists AND 18 | ${var.string} -len >8 AND 19 | ${var.string} -contains "dolor sit amet" AND 20 | ${var.string_array_elem_1} -eq "consectetur adipiscing elit" AND 21 | ${var.string_array_elem_2} -neq ${var.string}; 22 | 23 | SET 24 | ${var.test_strings} ["consectetur adipiscing elit", ${var.string_array_elem_2}] 25 | EXPECT 26 | ${var.string} -nin ${var.test_strings}; 27 | -------------------------------------------------------------------------------- /.qapir/tests/type_tests/types_test.yml: -------------------------------------------------------------------------------- 1 | name: Validate all types 2 | 3 | test_steps: 4 | - description: GET /types 5 | type: http 6 | url: ${env.url}/types 7 | method: GET 8 | expected_http_status: 200 9 | execute: | 10 | SELECT 11 | $.data.number AS ${var.number} AND 12 | $.data.string AS ${var.string} AND 13 | $.data.bool AS ${var.bool} AND 14 | $.data.object AS ${var.object} AND 15 | $.data.string_array AS ${var.string_array} AND 16 | $.data.string_array[0] AS ${var.string_array_elem} AND 17 | $.data.number_array AS ${var.number_array} AND 18 | $.data.number_array[0] AS ${var.number_array_elem} AND 19 | $.data.object_array AS ${var.object_array} AND 20 | $.data.object_array[0] AS ${var.object_array_elem} 21 | FROM 22 | ${response.body} 23 | EXPECT 24 | ${var.number} -type number AND 25 | ${var.string} -type string AND 26 | ${var.bool} -type bool AND 27 | ${var.object} -type object AND 28 | ${var.string_array} -type array AND 29 | ${var.string_array_elem} -type string AND 30 | ${var.number_array} -type array AND 31 | ${var.number_array_elem} -type number AND 32 | ${var.object_array} -type array AND 33 | ${var.object_array_elem} -type object; 34 | -------------------------------------------------------------------------------- /EULA.txt: -------------------------------------------------------------------------------- 1 | QAPIR SOFTWARE LICENSE TERMS 2 | ================================ 3 | 4 | These license terms are an agreement between you and Vertex Labs 5 | Inc. registered in Canada (or one of its affiliates). They apply to the Software 6 | named "qapir" and any Software updates (except to 7 | the extent such updates are accompanied by new or additional 8 | terms, in which case those different terms apply prospectively and do 9 | not alter your rights or rights of Vertex Labs Inc. relating to pre-updated Software). 10 | 11 | IF YOU COMPLY WITH THESE LICENSE TERMS, YOU HAVE THE RIGHTS 12 | BELOW. BY DOWNLOADING OR USING THE SOFTWARE, YOU ACCEPT THESE TERMS. 13 | 14 | 1. OWNERSHIP. 15 | 16 | The Software and any accompanying documentation are the exclusive property 17 | of Vertex Labs Inc. and its licensors. All rights, title, and interest 18 | in and to the Software, including but not limited to all intellectual property 19 | rights, are owned by Vertex Labs Inc. This includes, without limitation, 20 | any patents, copyrights, trademarks, and trade secrets related to the Software. 21 | 22 | 2. COPYRIGHT NOTICE. 23 | 24 | The Software is protected by copyright laws and international copyright treaties, 25 | as well as other intellectual property laws and treaties. You acknowledge that 26 | the Software and any copies that you are authorized to make are owned 27 | by Vertex Labs Inc. and/or its licensors and that you do not acquire 28 | any ownership rights by using or copying the Software. 29 | 30 | 3. TRADEMARKS. 31 | 32 | “Qapir”, “qapir” and any other product or service names or slogans contained in 33 | the Software are trademarks or registered trademarks of Vertex Labs Inc. 34 | You may not use these trademarks without the express written 35 | permission of Vertex Labs Inc. 36 | 37 | 4. NO TRANSFER OF OWNERSHIP. 38 | 39 | Nothing in this Agreement shall be construed as transferring any ownership rights 40 | or granting any licenses to any intellectual property of Vertex Labs Inc. 41 | other than the limited rights to use the Software as expressly provided herein. 42 | All rights not expressly granted in this Agreement are reserved by Vertex Labs Inc. 43 | 44 | 5. INSTALLATION AND USE RIGHTS. 45 | 46 | You may install and use any number of copies of the Software to test your applications. 47 | 48 | 6. SCOPE OF LICENSE. 49 | 50 | You will not (and have no right to): 51 | 52 | a. work around any technical limitations in the Software that only 53 | allow you to use it in certain ways; 54 | 55 | b. reverse engineer, decompile, or disassemble the Software, or 56 | attempt to do so; 57 | 58 | c. use the Software in any way that is against the law or to create 59 | or propagate malware; or 60 | 61 | d. share, publish, distribute, or lend the Software, provide the 62 | Software as a stand-alone hosted solution for others to use. 63 | 64 | 7. EXPORT RESTRICTIONS. 65 | 66 | You must comply with all domestic and 67 | international export laws and regulations that apply to the Software, 68 | which include restrictions on destinations, end users, and end use. 69 | 70 | 8. SUPPORT SERVICES. 71 | 72 | Vertex Labs Inc. is not obligated under this agreement to 73 | provide any support services for the Software. Any support provided 74 | is “as is”, “with all faults”, and without warranty of any kind. 75 | 76 | 9. UPDATES. 77 | 78 | You may obtain updates only from sources authorized by Vertex Labs Inc. 79 | Updates may not include or support all existing Software features, services, 80 | or peripheral devices. 81 | 82 | 10. ENTIRE AGREEMENT. 83 | 84 | This agreement, and any other terms Vertex Labs Inc. may 85 | provide for supplements or updates is the entire agreement for the Software. 86 | 87 | 11. DISCLAIMER OF WARRANTY. 88 | 89 | THE SOFTWARE IS PROVIDED “AS IS.” YOU BEAR 90 | THE RISK OF USING IT. VERTEX LABS INC. GIVES NO EXPRESS WARRANTIES, 91 | GUARANTEES, OR CONDITIONS. TO THE EXTENT PERMITTED UNDER APPLICABLE 92 | LAWS, VERTEX LABS INC. EXCLUDES ALL IMPLIED WARRANTIES, INCLUDING 93 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, AND 94 | NON-INFRINGEMENT. 95 | 96 | 12. LIMITATION ON AND EXCLUSION OF DAMAGES. 97 | 98 | IF YOU HAVE ANY BASIS FOR 99 | RECOVERING DAMAGES DESPITE THE PRECEDING DISCLAIMER OF WARRANTY, YOU 100 | CAN RECOVER FROM VERTEX LABS INC. ONLY DIRECT DAMAGES UP 101 | TO U.S. $1.00. YOU CANNOT RECOVER ANY OTHER DAMAGES, INCLUDING 102 | CONSEQUENTIAL, LOST PROFITS, SPECIAL, INDIRECT, OR INCIDENTAL 103 | DAMAGES. 104 | 105 | This limitation applies to (a) anything related to the Software, 106 | content (including code) on third party Internet sites, or 107 | third party applications; and (b) claims for breach of contract, 108 | warranty, guarantee, or condition; strict liability, negligence, or 109 | other tort; or any other claim; in each case to the extent permitted 110 | by applicable law. 111 | 112 | It also applies even if Vertex Labs Inc. knew or should have known about 113 | the possibility of the damages. The above limitation or exclusion 114 | may not apply to you because your state, province, or country may 115 | not allow the exclusion or limitation of incidental, consequential, 116 | or other damages. 117 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # About 2 | 3 | `Qapir` (pronounced `K-P-R`) is a comprehensive no-code tool for API and Backend Testing. 4 | 5 | Akin to a Swiss Army Knife in the world of Backend Testing, it provides: 6 | 7 | * Human-readable syntax for test-scenarios 8 | * Integrated reporting 9 | * Support for complex multi-step scenarios 10 | * Customizable retries for tests and test-steps 11 | * Test parameterization 12 | * Testing of REST and GraphQL APIs 13 | * Integrated HTTP-mocking 14 | 15 | ...And many other useful features that simplify Backend Testing! 16 | 17 | As a lightweight binary, `Qapir` functions effectively as a local development tool and as a test runner that easily fits into Continuous Integration pipelines. 18 | 19 | It enables users to define test-scenarios in a clear and readable YAML-based syntax, execute individual tests or entire test suites, and generate detailed human-readable reports. All with just a single command! 20 | 21 | # Docs 22 | Explore our detailed documentation at https://docs.qapir.io and start testing in just minutes! 23 | 24 | # Examples 25 | 26 | 1. [Writing tests](https://github.com/vrtxlabs/qapir/tree/main/.qapir) 27 | 28 | 2. Writing a [GitHub Action](https://github.com/vrtxlabs/qapir/blob/main/.github/workflows/qapir-demo.yml) that runs 29 | tests - see its execution [here](https://github.com/vrtxlabs/qapir/actions/runs/12736436705/job/35496318230#step:4:1) 30 | 31 | 3. Navigating the HTML report 32 | 33 | https://github.com/user-attachments/assets/fbbc812f-146e-46ee-80ae-bb0d67ab900a 34 | 35 | # Demo 36 | 37 | [This guide](https://docs.qapir.io/quickstart) explains how to execute a series of predefined tests on the provided demo-app. By following 38 | these instructions, you'll quickly experience the look and feel of `qapir`. 39 | 40 | # Installation 41 | 42 | Step 1: Download an archive with the latest release from this GitHub page - choose the build with the architecture matching 43 | your setup [here](https://github.com/vrtxlabs/qapir/releases/latest) 44 | 45 | **Linux amd64** 46 | 47 | ``` 48 | wget https://github.com/vrtxlabs/qapir/releases/latest/download/qapir-linux-amd64.tgz 49 | ``` 50 | 51 | **Mac arm64** 52 | 53 | ``` 54 | curl -LO https://github.com/vrtxlabs/qapir/releases/latest/download/qapir-darwin-arm64.tgz 55 | ``` 56 | 57 | Step 2: Unzip the archive 58 | 59 | **Linux amd64** 60 | 61 | ``` 62 | sudo tar -zxvf qapir-linux-amd64.tgz -C /opt/ 63 | ``` 64 | 65 | **Mac arm64** 66 | 67 | ``` 68 | sudo tar -zxvf qapir-darwin-arm64.tgz -C /opt/ 69 | ``` 70 | 71 | Step 3: Create a symlink to make `qapir` globally-available 72 | 73 | ``` 74 | sudo ln -s /opt/qapir /usr/local/bin/qapir 75 | ``` 76 | 77 | Step 4: (Optional) Delete the archive 78 | 79 | **Linux amd64** 80 | 81 | ``` 82 | rm -rf qapir-linux-amd64.tgz 83 | ``` 84 | 85 | **Mac arm64** 86 | 87 | ``` 88 | rm -rf qapir-darwin-arm64.tgz 89 | ``` 90 | 91 | # Known limitations 92 | 93 | 1. `jsonpath` expressions with multiple filters are not supported. As a workaround, filter by one criterion, store the 94 | value in a variable, and then filter by the next criterion. 95 | 2. `jsonpath` expressions that filter based on the absence of a field are not supported at this time, and there is no 96 | available workaround. 97 | 3. `qtl` statements are not validated for correctness yet. Please refer to the examples provided in this repository for 98 | guidance. 99 | --------------------------------------------------------------------------------