├── .docs ├── CHANGELOG.md ├── CONTRIBUTING.md └── img │ ├── fork.png │ ├── mocked_response.png │ ├── my_unicorn.png │ ├── pipeline.png │ ├── slack_pipeline_fail.png │ ├── slack_pipeline_success.png │ └── structure.gif ├── .github ├── ISSUE_TEMPLATE │ ├── bug_report.md │ ├── enhancement.md │ └── need-help-with-test-automation-bootstrap.md ├── no-response.yml ├── pull_request_template.md └── workflows │ ├── pull_request.yml │ ├── release-candidate.yml │ └── release.yml ├── .gitignore ├── LICENSE ├── README.md ├── docker-images ├── README.md ├── jdk11-mvn3.6.3.Dockerfile ├── jdk11.Dockerfile ├── python3.Dockerfile └── release-example.sh ├── elastic-stack ├── .env ├── Makefile ├── README.md ├── docker-compose.setup.yml ├── docker-compose.yml ├── elasticsearch │ ├── Dockerfile │ ├── config │ │ └── elasticsearch.yml │ └── scripts │ │ └── docker-healthcheck ├── heartbeat │ ├── Dockerfile │ └── config │ │ └── heartbeat.yml ├── kibana │ ├── Dockerfile │ ├── config │ │ └── kibana.yml │ └── scripts │ │ └── docker-healthcheck ├── logstash │ ├── Dockerfile │ ├── config │ │ ├── logstash.yml │ │ └── pipelines.yml │ └── pipeline │ │ ├── main.conf │ │ └── patterns │ │ ├── browser │ │ └── status ├── secrets │ ├── certs │ │ └── .gitkeep │ └── keystore │ │ └── .gitkeep └── setup │ ├── instances.yml │ ├── keystore.sh │ ├── setup-certs.sh │ └── setup-keystore.sh ├── jenkins ├── Jenkinsfile └── README.md ├── load-tests ├── README.md └── k6s.js ├── selenium-grid ├── README.md └── docker-compose.yml ├── slack ├── README.md └── slack_notifier.py ├── sonarqube ├── Dockerfile ├── README.md ├── docker-compose.yml └── sonar.properties └── ui-tests ├── README.md ├── pom.xml └── src ├── main ├── java │ └── io │ │ └── company │ │ └── .gitkeep └── resources │ └── .gitkeep └── test ├── java └── io │ └── company │ ├── pageobjects │ ├── PageObject.java │ ├── components │ │ ├── BaseComponent.java │ │ └── SearchComponent.java │ └── pages │ │ ├── BasePage.java │ │ ├── GoogleResultsPage.java │ │ └── GoogleSearchPage.java │ ├── tests │ ├── Base.java │ ├── BaseTest.java │ └── examples │ │ ├── ExampleMockedTest.java │ │ └── GoogleSearchTest.java │ └── utils │ ├── api │ ├── ClientApi.java │ ├── auth │ │ ├── AuthApi.java │ │ └── model │ │ │ └── Authentication.java │ └── user │ │ ├── UserApi.java │ │ └── model │ │ └── User.java │ ├── config │ ├── CustomConfiguration.java │ └── CustomConfigurationHolder.java │ ├── constants │ ├── Constants.java │ └── Urls.java │ ├── listeners │ ├── DistributedReportListener.java │ ├── ExtentReportListener.java │ └── MockListener.java │ ├── logging │ ├── AsJson.java │ └── Loggable.java │ ├── mocks │ ├── Mock.java │ ├── MockDefinition.java │ └── model │ │ └── MockExampleModel.java │ └── reports │ ├── ExtentManager.java │ └── ExtentTestReport.java └── resources ├── checkstyle └── checkstyle.xml ├── logback.xml ├── reports └── extent-config.xml └── suites ├── suiteA.xml └── suiteB.xml /.docs/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ## Change Log 2 | 3 | ### upcoming (2021/02/20 14:48 +00:00) 4 | - [#52](https://github.com/sergiomartins8/test-automation-bootstrap/pull/52) 🤖🐳 Selenium grid health-check (#52) (@sergiomartins8) 5 | - [#51](https://github.com/sergiomartins8/test-automation-bootstrap/pull/51) 📚 Revamp documentation (#51) (@sergiomartins8) 6 | - [#50](https://github.com/sergiomartins8/test-automation-bootstrap/pull/50) ☀️ Load tests (#50) (@sergiomartins8) 7 | 8 | ### v2.6.0 (2021/01/10 18:36 +00:00) 9 | - [#48](https://github.com/sergiomartins8/test-automation-bootstrap/pull/48) 🤖 Slack custom notifications! (#48) (@sergiomartins8) 10 | - [#43](https://github.com/sergiomartins8/test-automation-bootstrap/pull/43) 📚 Improve documentation and overall structure (#43) (@sergiomartins8) 11 | - [#35](https://github.com/sergiomartins8/test-automation-bootstrap/pull/35) ☀️ Healthcheck monitoring! (#35) (@sergiomartins8) 12 | 13 | ### v2.5.0 (2020/12/27 14:48 +00:00) 14 | - [#34](https://github.com/sergiomartins8/test-automation-bootstrap/pull/34) ☀️ Distributed Test Reporting (#34) (@sergiomartins8) 15 | 16 | ### v2.4.0 (2020/11/14 13:57 +00:00) 17 | - [#33](https://github.com/sergiomartins8/test-automation-bootstrap/pull/33) ☀️ Mock multiple requests! (#33) (@sergiomartins8) 18 | 19 | ### v2.2.0 (2020/10/16 17:29 +00:00) 20 | - [#32](https://github.com/sergiomartins8/test-automation-bootstrap/pull/32) ☀️ Revamp README.md (#32) (Sérgio Martins) 21 | 22 | ### v2.1.0 (2020/08/16 18:14 +00:00) 23 | - [#31](https://github.com/sergiomartins8/test-automation-bootstrap/pull/31) ☀️ Api utils (#31) (Sérgio Martins) 24 | - [#30](https://github.com/sergiomartins8/test-automation-bootstrap/pull/30) ⬆️ Upgrade framework to version 2.0 (#30) (Sérgio Martins) 25 | - [#29](https://github.com/sergiomartins8/test-automation-bootstrap/pull/29) 🐛 Fix logback configuration to default package (#29) (Sérgio Martins) 26 | 27 | ### v1.6.1 (2020/07/02 08:53 +00:00) 28 | - [#29](https://github.com/sergiomartins8/test-automation-bootstrap/pull/29) 🐛 Fix logback configuration to default package (#29) (Sérgio Martins) 29 | 30 | ### v1.6.0 (2020/06/22 18:02 +00:00) 31 | - [#27](https://github.com/sergiomartins8/test-automation-bootstrap/pull/27) ⬆️ Upgrade to selenide:5.12.2 (#27) (Sérgio Martins) 32 | 33 | ### v1.5.0 (2020/05/26 10:16 +00:00) 34 | - [#26](https://github.com/sergiomartins8/test-automation-bootstrap/pull/26) ⬆️ Upgrade to selenide:5.12.1 (#26) (Sérgio Martins) 35 | - [#25](https://github.com/sergiomartins8/test-automation-bootstrap/pull/25) 🔥 Revamp structure (#25) (Sérgio Martins) 36 | 37 | ### v1.4.0 (2020/05/24 14:38 +00:00) 38 | - [#24](https://github.com/sergiomartins8/test-automation-bootstrap/pull/24) ☀️ Travis Integration (#24) (Sérgio Martins) 39 | 40 | ### v1.3.0 (2020/05/21 08:32 +00:00) 41 | - [#23](https://github.com/sergiomartins8/test-automation-bootstrap/pull/23) ☀️ Jenkins Integration (#23) (Sérgio Martins) 42 | - [#22](https://github.com/sergiomartins8/test-automation-bootstrap/pull/22) 🤖 Revamp CI/CD pipelines (#22) (Sérgio Martins) 43 | 44 | ### v1.2.0 (2020/05/11 15:28 +00:00) 45 | - [#21](https://github.com/sergiomartins8/test-automation-bootstrap/pull/21) ✅ Refactor test examples (#21) (Sérgio Martins) 46 | 47 | ### v1.1.0 (2020/05/09 17:49 +00:00) 48 | - [#20](https://github.com/sergiomartins8/test-automation-bootstrap/pull/20) 🐛 Fix firefox integration tests (#20) (@sergiomartins8) 49 | 50 | ### v1.0.0 (2020/05/06 06:59 +00:00) 51 | - [#18](https://github.com/sergiomartins8/test-automation-bootstrap/pull/18) ☀️ Moving to archetype (#18) (@sergiomartins8) 52 | - [#16](https://github.com/sergiomartins8/test-automation-bootstrap/pull/16) 🤖 Github actions CI (#16) (Sérgio Martins) 53 | - [#15](https://github.com/sergiomartins8/test-automation-bootstrap/pull/15) CI/CD - github actions (#15) (Sérgio Martins) 54 | - [#7](https://github.com/sergiomartins8/test-automation-bootstrap/pull/7) add browser to system properties (#7) (Sérgio Martins) 55 | - [#8](https://github.com/sergiomartins8/test-automation-bootstrap/pull/8) Upgrade Extent Reports to version 4 (#8) (Sérgio Martins) 56 | - [#10](https://github.com/sergiomartins8/test-automation-bootstrap/pull/10) revamp mock listener and mock model (#10) (Sérgio Martins) 57 | - [#13](https://github.com/sergiomartins8/test-automation-bootstrap/pull/13) revamp current system properties management (#13) (Sérgio Martins) -------------------------------------------------------------------------------- /.docs/CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | 👍🎉 First off, thanks for taking the time to contribute! 🎉👍 2 | 3 | The following is a set of guidelines for contributing to test-automation-bootstrap. 4 | These are mostly guidelines, not rules. Use your best judgment, and feel free to propose changes to this document in a pull request. 5 | 6 | #### Table of contents 7 | 8 | [I just have a question!](#i-just-have-a-question) 9 | 10 | [How can I contribute?](#how-can-i-contribute) 11 | * [Reporting Bugs](#reporting-bugs) 12 | * [Suggesting Enhancements](#suggesting-enhancements) 13 | * [Your First Code Contribution](#your-first-code-contribution) 14 | * [Pull Requests](#pull-requests) 15 | 16 | [Styleguide](#styleguide) 17 | * [Git Commit Messages](#git-commit-messages) 18 | 19 | ## I just have a question! 20 | 21 | Questions and/or discussions are tracked as [issues](https://github.com/sergiomartins8/test-automation-bootstrap/issues). 22 | Fill in [the template](../.github/ISSUE_TEMPLATE/need-help-with-test-automation-bootstrap.md). 23 | 24 | ## How can I contribute? 25 | 26 | ### Reporting bugs 27 | 28 | Bug reports are tracked as [issues](https://github.com/sergiomartins8/test-automation-bootstrap/issues). 29 | Fill in [the template](../.github/ISSUE_TEMPLATE/bug_report.md), the information it asks for helps to resolve issues faster. 30 | 31 | > **Note:** If you find a **closed** issue that seems like it is the same thing that you're experiencing, open a new issue and include a link to the original issue in the body of your new one. 32 | 33 | ### Suggesting Enhancements 34 | 35 | Enhancement suggestions are tracked as [issues](https://github.com/sergiomartins8/test-automation-bootstrap/issues). 36 | Fill in [the template](../.github/ISSUE_TEMPLATE/enhancement.md), including the steps that you imagine you would take if the feature you're requesting existed. 37 | 38 | ### Your first code contribution 39 | 40 | Unsure where to begin contributing? You can start by looking for **utils** methods or classes you would like to see implemented: 41 | 42 | * Database connections (e.g. MongoDB, PostgreSQL) 43 | * File readers (e.g. Excel, PDF) 44 | 45 | ### Pull Requests 46 | 47 | The process described here has several goals: 48 | 49 | - Maintain test-automation-bootstrap's quality 50 | - Fix problems that are important to users 51 | - Engage the community in working toward the best possible test automation template ✨ 52 | - Enable a sustainable system for maintaining and review contributions 53 | 54 | Please follow these steps to have your contribution considered: 55 | 56 | 1. Follow all instructions in [the template](../.github/pull_request_template.md) 57 | 1. Follow the [styleguide](#styleguide) 58 | 3. After you submit your pull request, verify that all [status checks](https://help.github.com/articles/about-status-checks/) are passing 59 | 60 |
61 | What if the status checks are failing? 62 | If a status check is failing, and you believe that the failure is unrelated to your change, please leave a comment on the pull request explaining why you believe the failure is unrelated. 63 | If it's concluded that the failure was a false positive, then an issue will be opened to track that problem. 64 |
65 | 66 | 67 | While the prerequisites above must be satisfied prior to having your pull request reviewed, the reviewer(s) may ask you to complete additional design work, tests, or other changes before your pull request can be ultimately accepted. 68 | 69 | ## Styleguide 70 | 71 | ### Git Commit Messages 72 | 73 | * Use the present tense ("Add feature" not "Added feature") 74 | * Use the imperative mood ("Move cursor to..." not "Moves cursor to...") 75 | * Limit the first line to 72 characters or less 76 | * Reference issues and pull requests liberally after the first line 77 | * Consider starting the commit message with an applicable emoji: 78 | * ☀️ `:sun:` when adding a new feature 79 | * 🐛 `:bug:` when fixing a bug 80 | * 🔥 `:fire:` when removing code or files 81 | * 🤖 `:robot:` when fixing the CI build 82 | * ✅ `:white_check_mark:` when adding tests 83 | * ⬆️ `:arrow_up:` when upgrading dependencies 84 | * ⬇️ `:arrow_down:` when downgrading dependencies 85 | -------------------------------------------------------------------------------- /.docs/img/fork.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sergiomartins8/test-automation-bootstrap/bf93b17702e71fc9589a3318870515b2299f66ba/.docs/img/fork.png -------------------------------------------------------------------------------- /.docs/img/mocked_response.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sergiomartins8/test-automation-bootstrap/bf93b17702e71fc9589a3318870515b2299f66ba/.docs/img/mocked_response.png -------------------------------------------------------------------------------- /.docs/img/my_unicorn.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sergiomartins8/test-automation-bootstrap/bf93b17702e71fc9589a3318870515b2299f66ba/.docs/img/my_unicorn.png -------------------------------------------------------------------------------- /.docs/img/pipeline.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sergiomartins8/test-automation-bootstrap/bf93b17702e71fc9589a3318870515b2299f66ba/.docs/img/pipeline.png -------------------------------------------------------------------------------- /.docs/img/slack_pipeline_fail.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sergiomartins8/test-automation-bootstrap/bf93b17702e71fc9589a3318870515b2299f66ba/.docs/img/slack_pipeline_fail.png -------------------------------------------------------------------------------- /.docs/img/slack_pipeline_success.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sergiomartins8/test-automation-bootstrap/bf93b17702e71fc9589a3318870515b2299f66ba/.docs/img/slack_pipeline_success.png -------------------------------------------------------------------------------- /.docs/img/structure.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sergiomartins8/test-automation-bootstrap/bf93b17702e71fc9589a3318870515b2299f66ba/.docs/img/structure.gif -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | title: '' 5 | labels: 'type: triage/bug' 6 | assignees: sergiomartins8 7 | 8 | --- 9 | 10 | **Description** 11 | A clear and concise description of what the bug is. 12 | 13 | **How to Reproduce** 14 | Steps to reproduce the behavior. 15 | 16 | **Expected behavior** 17 | A clear and concise description of what you expected to happen. 18 | 19 | **Screenshots** 20 | If applicable, add screenshots to help explain your problem. 21 | 22 | **Additional context** 23 | Add any other context about the problem here. 24 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/enhancement.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Enhancement 3 | about: Suggest an idea for this project 4 | title: '' 5 | labels: 'type: triage/enhancement' 6 | assignees: sergiomartins8 7 | 8 | --- 9 | 10 | **Is your feature request related to a problem? Please describe.** 11 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] 12 | 13 | **Describe the solution you'd like to see applied** 14 | A clear and concise description of what you want to happen. 15 | 16 | **Describe alternatives you've considered** 17 | A clear and concise description of any alternative solutions or features you've considered. 18 | 19 | **Additional context** 20 | Add any other context or screenshots about the feature request here. 21 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/need-help-with-test-automation-bootstrap.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Need help with test-automation-bootstrap 3 | about: Ask a general how-to question. 4 | title: '' 5 | labels: 'type: triage/discussion' 6 | assignees: sergiomartins8 7 | 8 | --- 9 | 10 | 11 | -------------------------------------------------------------------------------- /.github/no-response.yml: -------------------------------------------------------------------------------- 1 | # Number of days of inactivity before an Issue is closed for lack of response 2 | daysUntilClose: 7 3 | # Label requiring a response 4 | responseRequiredLabel: "user: more info required" 5 | # Comment to post when closing an Issue for lack of response. Set to `false` to disable 6 | closeComment: > 7 | This issue has been automatically closed because there has been no response 8 | to our request for more information from the original author. 9 | -------------------------------------------------------------------------------- /.github/pull_request_template.md: -------------------------------------------------------------------------------- 1 | 10 | 11 | - [ ] This is a small change 12 | - [ ] This change has been discussed in issue # and the solution has been agreed upon with maintainers. 13 | 14 | --- 15 | 16 | **Description:** 17 | -------------------------------------------------------------------------------- /.github/workflows/pull_request.yml: -------------------------------------------------------------------------------- 1 | name: pull-request 2 | 3 | on: 4 | pull_request: 5 | branches: [ master ] 6 | 7 | jobs: 8 | build: 9 | runs-on: ubuntu-latest 10 | 11 | steps: 12 | - name: Checkout 13 | uses: actions/checkout@v2 14 | 15 | - name: Setup JDK 11 16 | uses: actions/setup-java@v1 17 | with: 18 | java-version: 11 19 | 20 | - name: Compile 21 | run: mvn -B -f ui-tests/pom.xml compile 22 | 23 | - name: Lint 24 | run: mvn -B -f ui-tests/pom.xml validate 25 | 26 | - name: Initialize containers 27 | run: docker-compose -f selenium-grid/docker-compose.yml up -d 28 | 29 | - name: Check available containers 30 | run: docker ps 31 | 32 | - name: Tests 33 | run: | 34 | mvn -B clean test \ 35 | -f ui-tests/pom.xml \ 36 | -Dselenide.proxyEnabled=true \ 37 | -Dselenide.headless=true \ 38 | -Dselenide.remote=http://0.0.0.0:4444/wd/hub \ 39 | -Dlistener=io/company/utils/listeners/MockListener.java 40 | -------------------------------------------------------------------------------- /.github/workflows/release-candidate.yml: -------------------------------------------------------------------------------- 1 | name: release-candidate 2 | 3 | on: 4 | push: 5 | branches: [ master ] 6 | 7 | jobs: 8 | build: 9 | runs-on: ubuntu-latest 10 | 11 | steps: 12 | - name: Checkout 13 | uses: actions/checkout@v2 14 | 15 | - name: Setup JDK 11 16 | uses: actions/setup-java@v1 17 | with: 18 | java-version: 11 19 | 20 | - name: Compile 21 | run: mvn -B -f ui-tests/pom.xml compile 22 | 23 | - name: Lint 24 | run: mvn -B -f ui-tests/pom.xml validate 25 | 26 | - name: Initialize containers 27 | run: docker-compose -f selenium-grid/docker-compose.yml up -d 28 | 29 | - name: Check available containers 30 | run: docker ps 31 | 32 | - name: Tests 33 | run: | 34 | mvn -B clean test \ 35 | -f ui-tests/pom.xml \ 36 | -Dselenide.proxyEnabled=true \ 37 | -Dselenide.headless=true \ 38 | -Dselenide.remote=http://0.0.0.0:4444/wd/hub \ 39 | -Dlistener=io/company/utils/listeners/MockListener.java 40 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: release 2 | 3 | on: 4 | release: 5 | types: [published] 6 | 7 | jobs: 8 | build: 9 | runs-on: ubuntu-latest 10 | 11 | steps: 12 | - name: Checkout 13 | uses: actions/checkout@v2 14 | 15 | - name: Setup JDK 11 16 | uses: actions/setup-java@v1 17 | with: 18 | java-version: 11 19 | 20 | - name: Compile 21 | run: mvn -B -f ui-tests/pom.xml compile 22 | 23 | - name: Lint 24 | run: mvn -B -f ui-tests/pom.xml validate 25 | 26 | - name: Initialize containers 27 | run: docker-compose -f selenium-grid/docker-compose.yml up -d 28 | 29 | - name: Check available containers 30 | run: docker ps 31 | 32 | - name: Tests 33 | run: | 34 | mvn -B clean test \ 35 | -f ui-tests/pom.xml \ 36 | -Dselenide.proxyEnabled=true \ 37 | -Dselenide.headless=true \ 38 | -Dselenide.remote=http://0.0.0.0:4444/wd/hub \ 39 | -Dlistener=io/company/utils/listeners/MockListener.java 40 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .idea/ 2 | .settings/ 3 | build/ 4 | .classpath 5 | target/ 6 | .project 7 | *.iml 8 | /reports/ExtentReport.html 9 | /.vscode 10 | secrets/ 11 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

Test Automation Bootstrap

2 | 3 |
4 | 5 | logo 6 | 7 |
8 | 9 | stars 10 | stars 11 | forks 12 | linkedin 13 | 14 | A simple and effective boilerplate repo to quickstart test automation frameworks 15 | 16 | releases 17 | Release build 18 | issues 19 | GitHub contributors 20 | license 21 | 22 |

23 | Explore the docs » 24 |
25 |
26 | Ask Question 27 | · 28 | Report Bug 29 | · 30 | Request Feature 31 |

32 | 33 |
34 | 35 | --- 36 | 37 | ## About 38 | The goal is to have this repo with boilerplate code in a way that anyone can quickstart a variety of automation frameworks, from functional to non-functional testing. 39 | 40 | ## Features 41 | * User interface testing (_java_) containing: 42 | * Code checkstyle 43 | * Customizable testing suites 44 | * Lombok (boilerplate code generator) 45 | * Capabilities to run mocked tests 46 | * HTML reports 47 | * Selenide (selenium webdriver library) 48 | * Selenium Grid 🐳 49 | * Jenkins templates (kubernetes compatible!) 50 | * Sonarqube 🐳 51 | * Elastic Stack 🐳 52 | * Distributed Test Reporting 53 | * Service Monitoring 54 | * Slack custom notifications 55 | * Load testing 56 | 57 | > 🐳 stands for _Dockerized_. 58 | 59 | ## Getting Started 🚀 60 | ![](.docs/img/fork.png) 61 | 62 | Fork this project, or clone it. That easy! 63 | 64 | ## Documentation 65 | You'll find a readme with all the documentation within each package. Those are mostly going to be guidelines, not rules. Use your best judgment, and feel free to propose changes or implement new features in a pull request. 66 | 67 | ## Changelog 68 | Available [here](.docs/CHANGELOG.md). 69 | 70 | > Automatically generated by using [github-changes](https://github.com/lalitkapoor/github-changes). 71 | 72 | ## Contributing 73 | Open source from the first commit ✨ 74 | 75 | Dive into the [contribution guide](.docs/CONTRIBUTING.md). 76 | -------------------------------------------------------------------------------- /docker-images/README.md: -------------------------------------------------------------------------------- 1 | ## Docker images 2 | 3 | Here you find useful and lightweight Dockerfiles. 4 | 5 | Furthermore, there is also a `release-example.sh` file that will guide you through on how to release your Docker image to any desired registry. 6 | -------------------------------------------------------------------------------- /docker-images/jdk11-mvn3.6.3.Dockerfile: -------------------------------------------------------------------------------- 1 | FROM alpine:3.11 2 | 3 | ENV JAVA_VERSION="11.0.5_p10-r0" 4 | ENV MAVEN_VERSION="3.6.3-r0" 5 | 6 | ENV JAVA_HOME /usr/lib/jvm/java-11-openjdk 7 | ENV PATH $PATH:/usr/lib/jvm/java-11-openjdk/jre/bin:/usr/lib/jvm/java-11-openjdk/bin 8 | 9 | RUN set -x \ 10 | && apk update \ 11 | && apk add --no-cache \ 12 | openjdk11="${JAVA_VERSION}" \ 13 | maven \ 14 | && rm -rf /var/cache/* \ 15 | && rm -rf /root/.cache/* 16 | 17 | CMD ["/bin/sh"] 18 | -------------------------------------------------------------------------------- /docker-images/jdk11.Dockerfile: -------------------------------------------------------------------------------- 1 | FROM alpine:3.11 2 | 3 | ENV JAVA_VERSION="11.0.5_p10-r0" 4 | 5 | ENV JAVA_HOME /usr/lib/jvm/java-11-openjdk 6 | ENV PATH $PATH:/usr/lib/jvm/java-11-openjdk/jre/bin:/usr/lib/jvm/java-11-openjdk/bin 7 | 8 | RUN set -x \ 9 | && apk update \ 10 | && apk add --no-cache \ 11 | openjdk11="${JAVA_VERSION}" \ 12 | && rm -rf /var/cache/* \ 13 | && rm -rf /root/.cache/* 14 | 15 | CMD ["/bin/sh"] 16 | -------------------------------------------------------------------------------- /docker-images/python3.Dockerfile: -------------------------------------------------------------------------------- 1 | FROM alpine:3.11 2 | 3 | RUN set -x \ 4 | && apk update \ 5 | && apk add --no-cache \ 6 | python3 \ 7 | && pip3 install --no-cache-dir --upgrade pip \ 8 | && rm -rf /var/cache/* \ 9 | && rm -rf /root/.cache/* 10 | 11 | CMD ["/bin/sh"] 12 | -------------------------------------------------------------------------------- /docker-images/release-example.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | set -eo pipefail 3 | 4 | DOCKERFILE=jdk11.Dockerfile 5 | DOCKER_REGISTRY=docker.io 6 | DOCKER_IMAGE=${DOCKER_REGISTRY}// 7 | DOCKER_TAG=1.0.0 8 | 9 | ## LOGIN 10 | echo 'Logging into docker hub' 11 | echo "${DOCKER_PASSWORD}" | docker login -u "${DOCKER_USERNAME}" --password-stdin 12 | 13 | ## BUILD IMAGE 14 | echo 'Building docker image' 15 | docker build -t ${DOCKER_IMAGE}:${DOCKER_TAG} -f ${DOCKERFILE} . 16 | 17 | ## TAG IMAGE 18 | echo 'Tagging image' 19 | docker tag ${DOCKER_IMAGE}:${DOCKER_TAG} ${DOCKER_IMAGE}:latest 20 | 21 | ## UPLOAD IMAGE 22 | echo 'Uploading image' 23 | docker push ${DOCKER_IMAGE}:${DOCKER_TAG} 24 | docker push ${DOCKER_IMAGE}:latest 25 | 26 | ## CLEAR WORKSPACE 27 | echo 'Clearing workspace' 28 | docker rmi ${DOCKER_IMAGE}:${DOCKER_TAG} 29 | docker rmi ${DOCKER_IMAGE}:latest 30 | -------------------------------------------------------------------------------- /elastic-stack/.env: -------------------------------------------------------------------------------- 1 | COMPOSE_PROJECT_NAME=elastic 2 | ELK_VERSION=7.10.1 3 | 4 | #----------- Resources --------------------------# 5 | ELASTICSEARCH_HEAP=256m 6 | LOGSTASH_HEAP=256m 7 | 8 | #----------- Hosts and Ports --------------------# 9 | # To be able to further "de-compose" the compose files, get hostnames from environment variables instead. 10 | 11 | ELASTICSEARCH_HOST=elasticsearch 12 | ELASTICSEARCH_PORT=9200 13 | 14 | KIBANA_HOST=kibana 15 | KIBANA_PORT=5601 16 | 17 | #----------- Credientals ------------------------# 18 | # Username & Password for Admin Elasticsearch cluster. 19 | # This is used to set the password at setup, and used by others to connect to Elasticsearch at runtime. 20 | ELASTIC_USERNAME=elastic 21 | ELASTIC_PASSWORD=kibana 22 | AWS_ACCESS_KEY_ID=nottherealid 23 | AWS_SECRET_ACCESS_KEY=notherealsecret 24 | 25 | #----------- Cluster ----------------------------# 26 | ELASTIC_CLUSTER_NAME=elastdocker-cluster 27 | ELASTIC_INIT_MASTER_NODE=elastdocker-node-0 28 | ELASTIC_NODE_NAME=elastdocker-node-0 29 | 30 | # Hostnames of master eligble elasticsearch instances. (matches compose generated host name) 31 | ELASTIC_DISCOVERY_SEEDS=elasticsearch 32 | -------------------------------------------------------------------------------- /elastic-stack/Makefile: -------------------------------------------------------------------------------- 1 | .DEFAULT_GOAL:=help 2 | 3 | COMPOSE_SETUP_FILE := -f docker-compose.setup.yml 4 | COMPOSE_FILE := -f docker-compose.yml 5 | 6 | ELK_MAIN_SERVICES := elasticsearch logstash kibana 7 | ELK_MONITORING := heartbeat 8 | 9 | ELK_ALL_SERVICES := ${ELK_MAIN_SERVICES} ${ELK_MONITORING} 10 | # -------------------------- 11 | 12 | # load .env so that Docker Swarm Commands has .env values too. (https://github.com/moby/moby/issues/29133) 13 | include .env 14 | export 15 | 16 | # -------------------------- 17 | .PHONY: setup keystore certs all elk monitoring tools build down stop restart rm logs 18 | 19 | keystore: ## Setup Elasticsearch Keystore, by initializing passwords, and add credentials defined in `keystore.sh`. 20 | docker-compose ${COMPOSE_SETUP_FILE} run --rm keystore 21 | 22 | certs: ## Generate Elasticsearch SSL Certs. 23 | docker-compose ${COMPOSE_SETUP_FILE} run --rm certs 24 | 25 | setup: ## Generate Elasticsearch SSL Certs and Keystore. 26 | @make certs 27 | @make keystore 28 | 29 | elk: ## Start ELK. 30 | docker-compose ${COMPOSE_FILE} up -d --build ${ELK_MAIN_SERVICES} 31 | 32 | monitoring: ## Start ELK Monitoring. 33 | @docker-compose ${COMPOSE_FILE} up -d --build ${ELK_MONITORING} 34 | 35 | build: ## Build ELK and all its extra components. 36 | @docker-compose ${COMPOSE_FILE} build ${ELK_ALL_SERVICES} 37 | 38 | down: ## Down ELK and all its extra components. 39 | @docker-compose ${COMPOSE_FILE} down ${ELK_ALL_SERVICES} 40 | 41 | stop: ## Stop ELK and all its extra components. 42 | @docker-compose ${COMPOSE_FILE} stop ${ELK_ALL_SERVICES} 43 | 44 | restart: ## Restart ELK and all its extra components. 45 | @docker-compose ${COMPOSE_FILE} restart ${ELK_ALL_SERVICES} 46 | 47 | rm: ## Remove ELK and all its extra components containers. 48 | @docker-compose $(COMPOSE_FILE) rm -f ${ELK_ALL_SERVICES} 49 | 50 | logs: ## Tail all logs with -n 1000. 51 | @docker-compose $(COMPOSE_FILE) logs --follow --tail=1000 ${ELK_ALL_SERVICES} 52 | 53 | images: ## Show all Images of ELK and all its extra components. 54 | @docker-compose $(COMPOSE_FILE) images ${ELK_ALL_SERVICES} 55 | 56 | prune: ## Remove ELK Containers and Delete Volume Data 57 | @make stop && make rm 58 | @docker volume prune -f 59 | 60 | help: ## Show this help. 61 | @echo "Make Application Docker Images and Containers using Docker-Compose files in 'docker' Dir." 62 | @awk 'BEGIN {FS = ":.*##"; printf "\nUsage:\n make \033[36m\033[0m (default: help)\n\nTargets:\n"} /^[a-zA-Z_-]+:.*?##/ { printf " \033[36m%-12s\033[0m %s\n", $$1, $$2 }' $(MAKEFILE_LIST) 63 | -------------------------------------------------------------------------------- /elastic-stack/README.md: -------------------------------------------------------------------------------- 1 | ## Elastic Stack 2 | 3 | ### Distributed Test Reporting 4 | 5 | What happens if you run multiple test suites in parallel? Would you want to get a single report for the whole run of a report for each failed suite? Also, what if they're all failing? 🤔👇 6 | 7 | ![](../.docs/img/pipeline.png) 8 | 9 | To solve the above (and other reporting vizualization issues), you can use ELK stack to serve as your reporting tool. It will provide you with a distributed log aggregator with an integrated visualization platform. 10 | 11 | > Check out the related distributed test reporting article on [medium](https://medium.com/@sergiomartins8/distributed-test-reporting-using-elk-stack-97dd699d6bb4). 12 | 13 | #### About 14 | Stack Version: [7.10.1](https://www.elastic.co/blog/elastic-stack-7-10-1-released) 15 | > You can change Elastic Stack version by setting `ELK_VERSION` in `.env` file and rebuild your images. Any version >= 7.0.0 is compatible with this template. 16 | 17 | This allows you to build your own distributed test reporting dashboards using pie charts, timeline analysis, and all other kinds of desired visualizations. 18 | The options are endless. 19 | 20 | #### Requirements 21 | - [Docker 17.05 or higher](https://docs.docker.com/install/) 22 | - [Docker-Compose 3 or higher](https://docs.docker.com/compose/install/) 23 | - 4GB RAM (For Windows and MacOS make sure Docker's VM has more than 4GB+ memory.) 24 | 25 | #### Setup 26 | 1. Initialize Elasticsearch Keystore and TLS Self-Signed Certificates 27 | ```shell script 28 | $ make setup 29 | ``` 30 | > **For Linux's docker hosts only**. By default virtual memory [is not enough](https://www.elastic.co/guide/en/elasticsearch/reference/current/vm-max-map-count.html) so run the next command as root `sysctl -w vm.max_map_count=262144` 31 | 2. Start ELK Stack 32 | ```shell script 33 | $ make elk $ docker-compose up -d 34 | ``` 35 | 3. Visit Kibana at [https://localhost:5601](https://localhost:5601) or `https://:5601` 36 | 37 | Default Username: `elastic`, Password: `kibana` 38 | 39 | > - Notice that Kibana is configured to use HTTPS, so you'll need to write `https://` before `localhost:5601` in the browser. 40 | > - Modify `.env` file for your needs, most importantly `ELASTIC_PASSWORD` that setup your superuser `elastic`'s password, `ELASTICSEARCH_HEAP` & `LOGSTASH_HEAP` for Elasticsearch & Logstash Heap Size. 41 | 42 | Whatever your host (e.g AWS EC2, Azure, DigitalOcean, or on-premise server), once you expose your host to the network, ELK component will be accessible on their respective ports. 43 | 44 | #### Setting Up Keystore 45 | You can extend the Keystore generation script by adding keys to `./setup/keystore.sh` script. (e.g Add S3 Snapshot Repository Credentials) 46 | 47 | To Re-generate Keystore: 48 | ```shell script 49 | $ make keystore 50 | ``` 51 | 52 | #### Enable SSL on HTTP 53 | By default, Transport Layer has SSL enabled as well as SSL on HTTP layer. 54 | 55 | > ⚠️ Since SSL on HTTP layer is enabled, it will require that all clients that connect to Elasticsearch have to configure SSL connection for HTTP, this includes all the current configured parts of the stack (e.g Logstash, Kibana, Curator, etc) plus any library/binding that connects to Elasticsearch from your application code. 56 | 57 | #### Example (based on the _[ui-tests](../ui-tests)_ boilerplate) 58 | In order to send out your logs to logstash use the [DistributedReportListener](../ui-tests/src/test/java/io/company/utils/listeners/DistributedReportListener.java) class. It has a base implementation, but tailor it accordingly. Execute as examplified below. 59 | 60 | ```shell script 61 | $ mvn clean test -Dlistener=${package}/utils/listeners/DistributedReportListener.java 62 | ``` 63 | 64 | ### Service Monitoring 65 | If you want to monitor multiple services to percieve their availability, you can use [heartbeat](https://www.elastic.co/beats/heartbeat) template which is already compatible with the ELK stack, described above, using the following commands: 66 | 67 | ```shell script 68 | $ make monitoring $ docker-compose up beartbeat -d 69 | ``` 70 | 71 | > **NOTE**: Edit the [heartbeat.yml](heartbeat/config/heartbeat.yml) configuration file according to your needs. 72 | -------------------------------------------------------------------------------- /elastic-stack/docker-compose.setup.yml: -------------------------------------------------------------------------------- 1 | version: '3.5' 2 | 3 | services: 4 | keystore: 5 | build: 6 | context: elasticsearch/ 7 | args: 8 | ELK_VERSION: ${ELK_VERSION} 9 | command: bash /setup/setup-keystore.sh 10 | user: "0" 11 | volumes: 12 | - ./secrets:/secrets 13 | - ./setup/:/setup/ 14 | environment: 15 | ELASTIC_PASSWORD: ${ELASTIC_PASSWORD} 16 | # Add keystore values used in `keystore.sh` here. (e.g AMAZON S3 repo credentials) 17 | AWS_ACCESS_KEY_ID: ${AWS_ACCESS_KEY_ID} 18 | AWS_SECRET_ACCESS_KEY: ${AWS_SECRET_ACCESS_KEY} 19 | 20 | certs: 21 | build: 22 | context: elasticsearch/ 23 | args: 24 | ELK_VERSION: ${ELK_VERSION} 25 | command: bash /setup/setup-certs.sh 26 | user: "0" 27 | volumes: 28 | - ./secrets:/secrets 29 | - ./setup/:/setup/ 30 | -------------------------------------------------------------------------------- /elastic-stack/docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: '3.5' 2 | 3 | # To Join any other app setup using another network, change name and set external = true 4 | networks: 5 | default: 6 | name: elastic 7 | external: false 8 | 9 | # will contain all elasticsearch data. 10 | volumes: 11 | elasticsearch-data: 12 | 13 | secrets: 14 | elasticsearch.keystore: 15 | file: ./secrets/keystore/elasticsearch.keystore 16 | elastic.ca: 17 | file: ./secrets/certs/ca/ca.crt 18 | elasticsearch.certificate: 19 | file: ./secrets/certs/elasticsearch/elasticsearch.crt 20 | elasticsearch.key: 21 | file: ./secrets/certs/elasticsearch/elasticsearch.key 22 | kibana.certificate: 23 | file: ./secrets/certs/kibana/kibana.crt 24 | kibana.key: 25 | file: ./secrets/certs/kibana/kibana.key 26 | 27 | services: 28 | elasticsearch: 29 | image: elasticsearch:test-automation-${ELK_VERSION} 30 | build: 31 | context: elasticsearch/ 32 | args: 33 | ELK_VERSION: ${ELK_VERSION} 34 | restart: unless-stopped 35 | environment: 36 | ELASTIC_USERNAME: ${ELASTIC_USERNAME} 37 | ELASTIC_PASSWORD: ${ELASTIC_PASSWORD} 38 | ELASTIC_CLUSTER_NAME: ${ELASTIC_CLUSTER_NAME} 39 | ELASTIC_NODE_NAME: ${ELASTIC_NODE_NAME} 40 | ELASTIC_INIT_MASTER_NODE: ${ELASTIC_INIT_MASTER_NODE} 41 | ELASTIC_DISCOVERY_SEEDS: ${ELASTIC_DISCOVERY_SEEDS} 42 | ES_JAVA_OPTS: -Xmx${ELASTICSEARCH_HEAP} -Xms${ELASTICSEARCH_HEAP} 43 | volumes: 44 | - elasticsearch-data:/usr/share/elasticsearch/data 45 | - ./elasticsearch/config/elasticsearch.yml:/usr/share/elasticsearch/config/elasticsearch.yml 46 | secrets: 47 | - source: elasticsearch.keystore 48 | target: /usr/share/elasticsearch/config/elasticsearch.keystore 49 | - source: elastic.ca 50 | target: /usr/share/elasticsearch/config/certs/ca.crt 51 | - source: elasticsearch.certificate 52 | target: /usr/share/elasticsearch/config/certs/elasticsearch.crt 53 | - source: elasticsearch.key 54 | target: /usr/share/elasticsearch/config/certs/elasticsearch.key 55 | ports: 56 | - "9200:9200" 57 | - "9300:9300" 58 | 59 | logstash: 60 | image: logstash:test-automation-${ELK_VERSION} 61 | build: 62 | context: logstash/ 63 | args: 64 | ELK_VERSION: $ELK_VERSION 65 | restart: unless-stopped 66 | volumes: 67 | - ./logstash/config/logstash.yml:/usr/share/logstash/config/logstash.yml:ro 68 | - ./logstash/config/pipelines.yml:/usr/share/logstash/config/pipelines.yml:ro 69 | - ./logstash/pipeline:/usr/share/logstash/pipeline:ro 70 | environment: 71 | ELASTIC_USERNAME: ${ELASTIC_USERNAME} 72 | ELASTIC_PASSWORD: ${ELASTIC_PASSWORD} 73 | ELASTICSEARCH_HOST_PORT: https://${ELASTICSEARCH_HOST}:${ELASTICSEARCH_PORT} 74 | LS_JAVA_OPTS: "-Xmx${LOGSTASH_HEAP} -Xms${LOGSTASH_HEAP}" 75 | secrets: 76 | - source: elastic.ca 77 | target: /certs/ca.crt 78 | ports: 79 | - "5000:5000/tcp" 80 | - "9600:9600" 81 | 82 | heartbeat: 83 | image: heartbeat:test-automation-${ELK_VERSION} 84 | build: 85 | context: heartbeat/ 86 | args: 87 | ELK_VERSION: $ELK_VERSION 88 | volumes: 89 | - type: bind 90 | source: ./heartbeat/config/heartbeat.yml 91 | target: /usr/share/heartbeat/heartbeat.yml 92 | read_only: true 93 | environment: 94 | ELASTIC_USERNAME: ${ELASTIC_USERNAME} 95 | ELASTIC_PASSWORD: ${ELASTIC_PASSWORD} 96 | ELASTICSEARCH_HOST_PORT: https://${ELASTICSEARCH_HOST}:${ELASTICSEARCH_PORT} 97 | KIBANA_HOST_PORT: https://${KIBANA_HOST}:${KIBANA_PORT} 98 | secrets: 99 | - source: elastic.ca 100 | target: /certs/ca.crt 101 | depends_on: 102 | - elasticsearch 103 | restart: unless-stopped 104 | 105 | kibana: 106 | image: kibana:test-automation-${ELK_VERSION} 107 | build: 108 | context: kibana/ 109 | args: 110 | ELK_VERSION: $ELK_VERSION 111 | restart: unless-stopped 112 | volumes: 113 | - ./kibana/config/:/usr/share/kibana/config:ro 114 | environment: 115 | ELASTIC_USERNAME: ${ELASTIC_USERNAME} 116 | ELASTIC_PASSWORD: ${ELASTIC_PASSWORD} 117 | ELASTICSEARCH_HOST_PORT: https://${ELASTICSEARCH_HOST}:${ELASTICSEARCH_PORT} 118 | secrets: 119 | - source: elastic.ca 120 | target: /certs/ca.crt 121 | - source: kibana.certificate 122 | target: /certs/kibana.crt 123 | - source: kibana.key 124 | target: /certs/kibana.key 125 | ports: 126 | - "5601:5601" 127 | -------------------------------------------------------------------------------- /elastic-stack/elasticsearch/Dockerfile: -------------------------------------------------------------------------------- 1 | ARG ELK_VERSION 2 | 3 | # https://github.com/elastic/elasticsearch-docker 4 | FROM docker.elastic.co/elasticsearch/elasticsearch:${ELK_VERSION} 5 | 6 | # Add healthcheck 7 | COPY scripts/docker-healthcheck . 8 | HEALTHCHECK CMD sh ./docker-healthcheck 9 | 10 | # Add your elasticsearch plugins setup here 11 | # Example: RUN elasticsearch-plugin install analysis-icu 12 | #RUN elasticsearch-plugin install --batch repository-s3 13 | -------------------------------------------------------------------------------- /elastic-stack/elasticsearch/config/elasticsearch.yml: -------------------------------------------------------------------------------- 1 | ## Default Elasticsearch configuration from Elasticsearch base image. 2 | ## https://github.com/elastic/elasticsearch/blob/master/distribution/docker/src/docker/config/elasticsearch.yml 3 | # 4 | cluster.name: ${ELASTIC_CLUSTER_NAME} 5 | node.name: ${ELASTIC_NODE_NAME} 6 | network.host: 0.0.0.0 7 | transport.host: 0.0.0.0 8 | 9 | ## Cluster Settings 10 | discovery.seed_hosts: ${ELASTIC_DISCOVERY_SEEDS} 11 | cluster.initial_master_nodes: ${ELASTIC_INIT_MASTER_NODE} 12 | 13 | ## License 14 | xpack.license.self_generated.type: basic 15 | 16 | # Security 17 | xpack.security.enabled: true 18 | 19 | ## - ssl 20 | xpack.security.transport.ssl.enabled: true 21 | xpack.security.transport.ssl.verification_mode: certificate 22 | xpack.security.transport.ssl.key: certs/elasticsearch.key 23 | xpack.security.transport.ssl.certificate: certs/elasticsearch.crt 24 | xpack.security.transport.ssl.certificate_authorities: certs/ca.crt 25 | 26 | ## - http 27 | xpack.security.http.ssl.enabled: true 28 | xpack.security.http.ssl.key: certs/elasticsearch.key 29 | xpack.security.http.ssl.certificate: certs/elasticsearch.crt 30 | xpack.security.http.ssl.certificate_authorities: certs/ca.crt 31 | xpack.security.http.ssl.client_authentication: optional 32 | 33 | # Monitoring 34 | xpack.monitoring.collection.enabled: true 35 | -------------------------------------------------------------------------------- /elastic-stack/elasticsearch/scripts/docker-healthcheck: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -eo pipefail 3 | 4 | host="$(hostname --ip-address || echo '127.0.0.1')" 5 | 6 | if health="$(curl -fsSL "http://$ELASTIC_USERNAME:$ELASTIC_PASSWORD@$host:9200/_cat/health?h=status")"; then 7 | health="$(echo "$health" | sed -r 's/^[[:space:]]+|[[:space:]]+$//g')" # trim whitespace (otherwise we'll have "green ") 8 | if [ "$health" = 'green' ] || [ "$health" = "yellow" ]; then 9 | exit 0 10 | fi 11 | echo >&2 "unexpected health status: $health" 12 | fi 13 | 14 | exit 1 15 | -------------------------------------------------------------------------------- /elastic-stack/heartbeat/Dockerfile: -------------------------------------------------------------------------------- 1 | ARG ELK_VERSION 2 | 3 | FROM docker.elastic.co/beats/heartbeat:${ELK_VERSION} 4 | -------------------------------------------------------------------------------- /elastic-stack/heartbeat/config/heartbeat.yml: -------------------------------------------------------------------------------- 1 | ## Default Heartbeat configuration from Elastic stack. 2 | ## https://www.elastic.co/guide/en/beats/heartbeat/current/heartbeat-reference-yml.html 3 | # 4 | heartbeat.monitors: 5 | - type: http 6 | name: DEV-SERVICE 7 | schedule: '@every 10s' 8 | urls: [ "https://my.awesome.api.com/v1/healthcheck" ] 9 | check.request: 10 | method: GET 11 | headers: 12 | 'authorization': 'your.auth.token' 13 | 'identity': 'your.other.auth.token' 14 | check.response: 15 | status: 200 16 | - type: http 17 | name: PROD-OTHER-SERVICE 18 | schedule: '@every 10s' 19 | urls: [ "https://my.other.awesome.api.com/v1/healthcheck" ] 20 | check.request: 21 | method: GET 22 | headers: 23 | 'authorization': 'your.auth.token' 24 | 'identity': 'your.other.auth.token' 25 | check.response: 26 | status: 200 27 | 28 | output.elasticsearch: 29 | hosts: [ "${ELASTICSEARCH_HOST_PORT}" ] 30 | protocol: "https" 31 | ssl: 32 | enabled: true 33 | verification_mode: none 34 | certificate_authorities: [ "/certs/ca.crt" ] 35 | username: "${ELASTIC_USERNAME}" 36 | password: "${ELASTIC_PASSWORD}" 37 | 38 | 39 | setup.kibana: 40 | host: [ "${KIBANA_HOST_PORT}" ] 41 | username: "${ELASTIC_USERNAME}" 42 | password: "${ELASTIC_PASSWORD}" 43 | -------------------------------------------------------------------------------- /elastic-stack/kibana/Dockerfile: -------------------------------------------------------------------------------- 1 | ARG ELK_VERSION 2 | 3 | # https://github.com/elastic/kibana-docker 4 | FROM docker.elastic.co/kibana/kibana:${ELK_VERSION} 5 | ARG ELK_VERSION 6 | 7 | # Add healthcheck 8 | COPY scripts/docker-healthcheck . 9 | HEALTHCHECK CMD sh ./docker-healthcheck 10 | 11 | # Add your kibana plugins setup here 12 | # Example: RUN kibana-plugin install 13 | #RUN kibana-plugin install https://github.com/bitsensor/elastalert-kibana-plugin/releases/download/1.1.0/elastalert-kibana-plugin-1.1.0-${ELK_VERSION}.zip -------------------------------------------------------------------------------- /elastic-stack/kibana/config/kibana.yml: -------------------------------------------------------------------------------- 1 | --- 2 | ## Default Kibana configuration from Kibana base image. 3 | ## https://github.com/elastic/kibana/blob/master/src/dev/build/tasks/os_packages/docker_generator/templates/kibana_yml.template.js 4 | # 5 | server.name: kibana 6 | server.host: "0.0.0.0" 7 | 8 | # Elasticsearch Connection 9 | elasticsearch.hosts: [ "${ELASTICSEARCH_HOST_PORT}" ] 10 | 11 | # SSL settings 12 | server.ssl.enabled: true 13 | server.ssl.certificate: /certs/kibana.crt 14 | server.ssl.key: /certs/kibana.key 15 | server.ssl.certificateAuthorities: [ "/certs/ca.crt" ] 16 | xpack.security.encryptionKey: C1tHnfrlfxSPxPlQ8BlgPB5qMNRtg5V5 17 | xpack.encryptedSavedObjects.encryptionKey: D12GTfrlfxSPxPlGRBlgPB5qM5GOPDV5 18 | xpack.reporting.encryptionKey: RSCueeHKzrqzOVTJhkjt17EMnzM96LlN 19 | 20 | ## X-Pack security credentials 21 | elasticsearch.username: ${ELASTIC_USERNAME} 22 | elasticsearch.password: ${ELASTIC_PASSWORD} 23 | elasticsearch.ssl.certificateAuthorities: [ "/certs/ca.crt" ] 24 | 25 | 26 | ## Misc 27 | elasticsearch.requestTimeout: 90000 28 | -------------------------------------------------------------------------------- /elastic-stack/kibana/scripts/docker-healthcheck: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -eo pipefail 3 | 4 | host="$(hostname --ip-address || echo '127.0.0.1')" 5 | if health="$(curl -fsSkL "https://$ELASTIC_USERNAME:$ELASTIC_PASSWORD@$host:5601/api/status" | python -c "import sys, json; print json.load(sys.stdin)['status']['overall']['state']")"; then 6 | health="$(echo "$health" | sed -r 's/^[[:space:]]+|[[:space:]]+$//g')" # trim whitespace (otherwise we'll have "green ") 7 | if [ "$health" = 'green' ]; then 8 | exit 0 9 | fi 10 | echo >&2 "unexpected health status: $health" 11 | fi 12 | exit 1 -------------------------------------------------------------------------------- /elastic-stack/logstash/Dockerfile: -------------------------------------------------------------------------------- 1 | ARG ELK_VERSION 2 | 3 | # https://github.com/elastic/logstash-docker 4 | FROM docker.elastic.co/logstash/logstash:${ELK_VERSION} 5 | 6 | HEALTHCHECK --interval=240s --timeout=120s --retries=5 \ 7 | CMD curl -s -XGET 'http://127.0.0.1:9600' 8 | 9 | # Add your logstash plugins setup here 10 | RUN logstash-plugin install logstash-filter-grok 11 | -------------------------------------------------------------------------------- /elastic-stack/logstash/config/logstash.yml: -------------------------------------------------------------------------------- 1 | --- 2 | http.host: "0.0.0.0" 3 | xpack.monitoring.elasticsearch.hosts: [ "${ELASTICSEARCH_HOST_PORT}" ] 4 | 5 | ## X-Pack security credentials 6 | xpack.monitoring.enabled: true 7 | xpack.monitoring.elasticsearch.username: ${ELASTIC_USERNAME} 8 | xpack.monitoring.elasticsearch.password: ${ELASTIC_PASSWORD} 9 | xpack.monitoring.elasticsearch.ssl.certificate_authority: /certs/ca.crt 10 | -------------------------------------------------------------------------------- /elastic-stack/logstash/config/pipelines.yml: -------------------------------------------------------------------------------- 1 | # For per pipeline config, check docs: https://www.elastic.co/guide/en/logstash/current/logstash-settings-file.html 2 | 3 | - pipeline.id: main 4 | path.config: "/usr/share/logstash/pipeline/main.conf" 5 | queue.type: memory 6 | -------------------------------------------------------------------------------- /elastic-stack/logstash/pipeline/main.conf: -------------------------------------------------------------------------------- 1 | input { 2 | tcp { 3 | port => 5000 4 | codec => json_lines 5 | } 6 | } 7 | 8 | filter { 9 | grok { 10 | patterns_dir => ["/usr/share/logstash/pipeline/patterns"] 11 | match => { 12 | "message" => 13 | "Pipeline: %{NUMBER:pipelineID:int} Browser: %{BROWSER:browser} Status: %{STATUS:status} Test: %{WORD:test} Suite: %{WORD:suite}( Trace: %{GREEDYDATA:trace})?" 14 | } 15 | } 16 | 17 | if "_grokparsefailure" in [tags] { 18 | drop { } 19 | } 20 | } 21 | 22 | output { 23 | elasticsearch { 24 | index => "e2e" 25 | document_type => "log" 26 | hosts => [ "${ELASTICSEARCH_HOST_PORT}" ] 27 | user => "${ELASTIC_USERNAME}" 28 | password => "${ELASTIC_PASSWORD}" 29 | ssl => true 30 | ssl_certificate_verification => false 31 | cacert => '/certs/ca.crt' 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /elastic-stack/logstash/pipeline/patterns/browser: -------------------------------------------------------------------------------- 1 | BROWSER \b(chrome|firefox|safari)\b 2 | -------------------------------------------------------------------------------- /elastic-stack/logstash/pipeline/patterns/status: -------------------------------------------------------------------------------- 1 | STATUS \b(failed|skipped|passed)\b 2 | -------------------------------------------------------------------------------- /elastic-stack/secrets/certs/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sergiomartins8/test-automation-bootstrap/bf93b17702e71fc9589a3318870515b2299f66ba/elastic-stack/secrets/certs/.gitkeep -------------------------------------------------------------------------------- /elastic-stack/secrets/keystore/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sergiomartins8/test-automation-bootstrap/bf93b17702e71fc9589a3318870515b2299f66ba/elastic-stack/secrets/keystore/.gitkeep -------------------------------------------------------------------------------- /elastic-stack/setup/instances.yml: -------------------------------------------------------------------------------- 1 | instances: 2 | - name: elasticsearch 3 | dns: 4 | - elasticsearch 5 | - localhost 6 | ip: 7 | - 127.0.0.1 8 | 9 | - name: kibana 10 | dns: 11 | - kibana 12 | - localhost 13 | ip: 14 | - 127.0.0.1 -------------------------------------------------------------------------------- /elastic-stack/setup/keystore.sh: -------------------------------------------------------------------------------- 1 | # Exit on Error 2 | set -e 3 | 4 | # Setting Bootstrap Password 5 | echo "Setting bootstrap.password..." 6 | (echo "$ELASTIC_PASSWORD" | elasticsearch-keystore add -x 'bootstrap.password') 7 | 8 | # ----- Setting Secrets 9 | 10 | ## Add Additional Config 11 | # 1- Copy the below commented block, uncomment it, and replace , , and . 12 | # 2- Pass to setup container in `docker-compose-setup.yml` 13 | 14 | ## Setting 15 | #echo "Setting ..." 16 | #(echo "$" | elasticsearch-keystore add -x '') 17 | 18 | 19 | # ----- Setting S3 Secrets 20 | 21 | ## Setting S3 Access Key 22 | #echo "Setting S3 Access Key..." 23 | #(echo "$AWS_ACCESS_KEY_ID" | elasticsearch-keystore add -x 's3.client.default.access_key') 24 | # 25 | ## Setting S3 Secret Key 26 | #echo "Setting S3 Secret Key..." 27 | #(echo "$AWS_SECRET_ACCESS_KEY" | elasticsearch-keystore add -x 's3.client.default.secret_key') -------------------------------------------------------------------------------- /elastic-stack/setup/setup-certs.sh: -------------------------------------------------------------------------------- 1 | # Exit on Error 2 | set -e 3 | 4 | OUTPUT_DIR=/secrets/certs 5 | ZIP_FILE=$OUTPUT_DIR/certs.zip 6 | 7 | printf "======= Generating Elastic Stack Certificates =======\n" 8 | printf "=====================================================\n" 9 | 10 | if ! command -v unzip &>/dev/null; then 11 | printf "Installing Necessary Tools... \n" 12 | yum install -y -q -e 0 unzip; 13 | fi 14 | 15 | printf "Clearing Old Certificates if exits... \n" 16 | find $OUTPUT_DIR -mindepth 1 -type d -exec rm -rf -- {} + 17 | rm -f $ZIP_FILE 18 | 19 | printf "Generating... \n" 20 | bin/elasticsearch-certutil cert --silent --pem --in /setup/instances.yml -out $ZIP_FILE &> /dev/null 21 | 22 | printf "Unzipping Certifications... \n" 23 | unzip -qq $ZIP_FILE -d $OUTPUT_DIR; 24 | 25 | printf "Applying Permissions... \n" 26 | chown -R 1000:0 $OUTPUT_DIR 27 | find $OUTPUT_DIR -type f -exec chmod 655 -- {} + 28 | 29 | printf "=====================================================\n" 30 | printf "SSL Certifications generation completed successfully.\n" 31 | printf "=====================================================\n" 32 | -------------------------------------------------------------------------------- /elastic-stack/setup/setup-keystore.sh: -------------------------------------------------------------------------------- 1 | # Exit on Error 2 | set -e 3 | 4 | OUTPUT_FILE=/secrets/keystore/elasticsearch.keystore 5 | NATIVE_FILE=/usr/share/elasticsearch/config/elasticsearch.keystore 6 | 7 | # Password Generate 8 | PW=$(head /dev/urandom | tr -dc A-Za-z0-9 | head -c 16 ;) 9 | ELASTIC_PASSWORD="${ELASTIC_PASSWORD:-$PW}" 10 | export ELASTIC_PASSWORD 11 | 12 | # Create Keystore 13 | printf "========== Creating Elasticsearch Keystore ==========\n" 14 | printf "=====================================================\n" 15 | elasticsearch-keystore create >> /dev/null 16 | 17 | # Setting Secrets 18 | echo "Elastic password is: $ELASTIC_PASSWORD" 19 | sh /setup/keystore.sh 20 | 21 | # Replace current Keystore 22 | if [ -f "$OUTPUT_FILE" ]; then 23 | echo "Remove old elasticsearch.keystore" 24 | rm $OUTPUT_FILE 25 | fi 26 | 27 | echo "Saving new elasticsearch.keystore" 28 | mv $NATIVE_FILE $OUTPUT_FILE 29 | chmod 0644 $OUTPUT_FILE 30 | 31 | printf "======= Keystore setup completed successfully =======\n" 32 | printf "=====================================================\n" 33 | printf "Remember to restart the stack, or reload secure settings if changed settings are hot-reloadable.\n" 34 | printf "About Reloading Settings: https://www.elastic.co/guide/en/elasticsearch/reference/current/secure-settings.html#reloadable-secure-settings\n" 35 | printf "Your 'elastic' user password is: $ELASTIC_PASSWORD\n" 36 | printf "=====================================================\n" -------------------------------------------------------------------------------- /jenkins/Jenkinsfile: -------------------------------------------------------------------------------- 1 | #!groovy 2 | 3 | /* 4 | * This is a Jenkinsfile example using Jenkins on Kubernetes. 5 | * 6 | * Follow this article to have a similar environment: 7 | * https://medium.com/@sergiomartins8/highly-scalable-jenkins-on-minikube-8cc289a31850 8 | * 9 | */ 10 | String SONARQUBE_ADDRESS = "sonarqube-address-example.com" 11 | String REMOTE_GRID_ADDRESS = "selenium-grid-address-example.com" 12 | 13 | /* 14 | * Jenkins slaves are built from a custom made docker image, which may be found at: 15 | * https://github.com/sergiomartins8/jenkins-slave-base 16 | * 17 | * Feel free to use your own or another one that fits your needs. 18 | * 19 | */ 20 | podTemplate(label: "jenkins-slave-base-pod", serviceAccount: "jenkins", containers: [ 21 | containerTemplate( 22 | name: "base", 23 | image: "sergiomartins8/jenkins-slave-base:latest", 24 | ttyEnabled: true, 25 | command: "cat" 26 | ) 27 | ], 28 | volumes: [ 29 | hostPathVolume(mountPath: "/var/run/docker.sock", hostPath: "/var/run/docker.sock") 30 | ] 31 | ) { 32 | node("jenkins-slave-base-pod") { 33 | container("base") { 34 | stage("Checkout") { 35 | checkout scm 36 | } 37 | stage("Build and Test") { 38 | parallel( 39 | "Lint": { 40 | sh "mvn -B validate" 41 | }, 42 | "SonarQube Analysis": { 43 | sh """mvn -B clean verify sonar:sonar \ 44 | -Dskip.validate=true \ 45 | -Dmaven.test.skip=true \ 46 | -Dsonar.host.url=${SONARQUBE_ADDRESS} \ 47 | -Dsonar.qualitygate.wait=true \ 48 | -Dsonar.sources=src/test/java \ 49 | -Dsonar.tests=src/main/java \ 50 | -Dsonar.inclusions=src/test/java/**/*.java \ 51 | -Dsonar.tests.exclusions=src/test/java/**/*.java""" 52 | }, 53 | "Compile": { 54 | sh "mvn -B compile" 55 | } 56 | ) 57 | } 58 | stage("Testing") { 59 | catchError(buildResult: 'UNSTABLE', stageResult: 'FAILURE') { 60 | sh """set -e; mvn -B test \ 61 | -Dselenide.remote=${REMOTE_GRID_ADDRESS} \ 62 | -Dlistener=io/company/utils/listeners/MockListener.java,io/company/utils/listeners/ExtentReportListener.java \ 63 | -Dselenide.browser=firefox \ 64 | -Dselenide.proxyEnabled=true""" 65 | } 66 | 67 | publishHTML (target: [ 68 | allowMissing: false, 69 | alwaysLinkToLastBuild: false, 70 | keepAll: true, 71 | reportDir: "reports", 72 | reportFiles: "ExtentReport.html", 73 | reportName: "ExtentReport" 74 | ]) 75 | } 76 | } 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /jenkins/README.md: -------------------------------------------------------------------------------- 1 | ## Jenkins 🤖 2 | Use this example to get started. However, it might need some tailoring. 3 | 4 | ##### Snippet 5 | ```groovy 6 | podTemplate(label: "jenkins-slave-base-pod", serviceAccount: "jenkins", containers: [ 7 | containerTemplate( 8 | name: "base", 9 | image: "sergiomartins8/jenkins-slave-base:latest", 10 | ttyEnabled: true, 11 | command: "cat" 12 | ) 13 | ], 14 | volumes: [ 15 | hostPathVolume(mountPath: "/var/run/docker.sock", hostPath: "/var/run/docker.sock") 16 | ] 17 | ) { 18 | node("jenkins-slave-base-pod") { 19 | container("base") { 20 | stage("Checkout") { 21 | checkout scm 22 | } 23 | 24 | (...) 25 | 26 | } 27 | } 28 | } 29 | ``` 30 | 31 | > The example above uses Jenkins on Kubernetes. Follow this [article](https://medium.com/@sergiomartins8/highly-scalable-jenkins-on-minikube-8cc289a31850) to have a similar environment in no time! 32 | > 33 | > Furthermore, the source code for the base image is open source and available [here](https://github.com/sergiomartins8/jenkins-slave-base). 34 | -------------------------------------------------------------------------------- /load-tests/README.md: -------------------------------------------------------------------------------- 1 | ## Load Tests 2 | Here you'll find examples using [K6](https://k6.io/) load testing tool. 3 | 4 | Following the k6 [documentation](https://k6.io/docs/getting-started/installation) for more details. 5 | 6 | > Snippet using gitlab pipelines 7 | ```yaml 8 | load_testing: 9 | stage: load-testing 10 | image: 11 | name: loadimpact/k6:latest 12 | entrypoint: [ '' ] 13 | script: | 14 | k6 run -e USER_EMAIL_ADDRESS=$USER_EMAIL_ADDRESS \ 15 | -e USER_PASSWORD=$USER_PASSWORD \ 16 | -e AWS_APP_CLIENT_ID=$AWS_APP_CLIENT_ID \ 17 | load-tests/k6.js 18 | ``` 19 | -------------------------------------------------------------------------------- /load-tests/k6s.js: -------------------------------------------------------------------------------- 1 | import http from 'k6/http'; 2 | import { group, check, sleep } from 'k6'; 3 | import { Rate } from 'k6/metrics'; 4 | 5 | // Global failure rate 6 | export let failureRate = new Rate('failures'); 7 | 8 | // Global options for the test 9 | // Example: Ramp up to 500 virtual users within 30s, keep the load for another 60s and finally ramp down for 30s till zero virtual users 10 | export let options = { 11 | stages: [ 12 | { 13 | target: 500, 14 | duration: '30s' 15 | }, 16 | { 17 | target: 500, 18 | duration: '60s' 19 | }, 20 | { 21 | target: 0, 22 | duration: '30s' 23 | } 24 | ], 25 | thresholds: { 26 | // Test fails if the 95th percentile of requests exceed 500ms 27 | 'http_req_duration{source:api}': ['p(95)<500'], 28 | 29 | failures: [ 30 | // Global failed checks rate should be less than 5% 31 | 'rate<0.05', 32 | ], 33 | }, 34 | }; 35 | 36 | // Just like a @BeforeTest annotation 37 | // Here you'll setup the necessary steps to perform your test 38 | // Example: Get access tokens to perform authenticated requests (AWS Cognito examplified here) 39 | export function setup() { 40 | const body = JSON.stringify({ 41 | 'AuthParameters': { 42 | 'USERNAME': `${__ENV.USER_EMAIL_ADDRESS}`, // ${USER_EMAIL_ADDRESS} 43 | 'PASSWORD': `${__ENV.USER_PASSWORD}`, // ${USER_PASSWORD} 44 | }, 45 | 'AuthFlow': 'USER_PASSWORD_AUTH', 46 | 'ClientId': `${__ENV.AWS_APP_CLIENT_ID}`, // ${AWS_APP_CLIENT_ID} 47 | }); 48 | 49 | const headers = { 50 | 'Content-Type': 'application/x-amz-json-1.1', 51 | 'X-Amz-Target': 'AWSCognitoIdentityProviderService.InitiateAuth', 52 | }; 53 | 54 | let response = http.post('https://cognito-idp.eu-west-1.amazonaws.com/', body, { headers: headers }); 55 | return JSON.parse(response.body); // result to be used by the test 56 | } 57 | 58 | // Simulate user behavior navigating/checking stuff on the page 59 | function simulateUserInteractionDelay() { 60 | sleep(1 + Math.random() * 2); 61 | } 62 | 63 | // The actual test 64 | // Note that the parameter "data" is the return of the "setup" method 65 | // Remove it if you don't use a "setup" 66 | export default function (data) { 67 | group('Load testing', function () { 68 | // Use as many groups as you'd like 69 | group('My test name grouped by logic just for the sake of proper test structure', function () { 70 | let response = http.get('https://my.awesome.api.com/users', { 71 | tags: { source: 'api' }, 72 | headers: { 73 | authorization: data.AuthenticationResult.AccessToken, 74 | identity: data.AuthenticationResult.IdToken 75 | } 76 | }); 77 | 78 | const result = check(response, { 79 | 'can list users': (r) => r.status === 200, 80 | }); 81 | 82 | failureRate.add(!result); 83 | }); 84 | 85 | simulateUserInteractionDelay(); 86 | }); 87 | } 88 | -------------------------------------------------------------------------------- /selenium-grid/README.md: -------------------------------------------------------------------------------- 1 | ## Selenium Grid 2 | Launch a Google Chrome and Firefox selenium grid with compose using `$ docker-compose up -d`. Then execute your tests. 3 | 4 | > Example using [ui-tests](../ui-tests) 5 | ```shell 6 | $ mvn -B clean test \ 7 | -Dselenide.browser=firefox \ 8 | -Dselenide.headless=true \ 9 | -Dselenide.remote=http://0.0.0.0:4444/wd/hub 10 | ``` 11 | -------------------------------------------------------------------------------- /selenium-grid/docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: "3" 2 | 3 | services: 4 | 5 | selenium-hub: 6 | image: selenium/hub:3.141 7 | container_name: selenium-hub 8 | ports: 9 | - "4444:4444" 10 | expose: 11 | - 4444 12 | healthcheck: 13 | test: [ "CMD", "wget", "--spider", "http://0.0.0.0:4444/wd/hub/status" ] 14 | interval: 5s 15 | timeout: 5s 16 | retries: 5 17 | 18 | chrome: 19 | image: selenium/node-chrome:3.141 20 | container_name: chrome 21 | ports: 22 | - "5900:5900" 23 | volumes: 24 | - /dev/shm:/dev/shm 25 | depends_on: 26 | selenium-hub: 27 | condition: service_healthy 28 | environment: 29 | - HUB_HOST=selenium-hub 30 | - HUB_PORT=4444 31 | 32 | firefox: 33 | image: selenium/node-firefox:3.141 34 | container_name: firefox 35 | ports: 36 | - "5901:5900" 37 | volumes: 38 | - /dev/shm:/dev/shm 39 | depends_on: 40 | selenium-hub: 41 | condition: service_healthy 42 | environment: 43 | - HUB_HOST=selenium-hub 44 | - HUB_PORT=4444 45 | -------------------------------------------------------------------------------- /slack/README.md: -------------------------------------------------------------------------------- 1 | ## Slack 2 | Nowadays you can integrate with slack using pretty much every git repository or CI tool. However, if you feel like you want to take the full control and setup your own custom notifications this is for you. Thus, follow the steps below in order to use the template provided. 3 | 4 | ### Create an app 5 | You'll need to create a slack bot app to post out notifications to your channels (install the app on the required channel if it's a private one). 6 | 7 | Follow this great [tutorial](https://github.com/slackapi/python-slack-sdk/tree/main/tutorial) in order to setup your slack bot app. 8 | 9 | ### Custom notifications 10 | Then you can integrate the [slack_notifier.py](slack_notifier.py) in your CI pipeline to post notifications as examplified below. 11 | 12 | ![](../.docs/img/slack_pipeline_fail.png) 13 | 14 | ![](../.docs/img/slack_pipeline_success.png) 15 | 16 | > Snippet using gitlab pipelines 17 | ```yaml 18 | nofity_results: 19 | stage: notify 20 | image: sergiomartins8/jenkins-slave-base:latest 21 | script: 22 | - pip install slack_sdk 23 | - ./scripts/slack/slack_notifier.py 24 | ``` 25 | -------------------------------------------------------------------------------- /slack/slack_notifier.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python3 2 | 3 | import os 4 | from slack_sdk import WebClient 5 | from slack_sdk.errors import SlackApiError 6 | 7 | token = os.getenv('SLACK_BOT_TOKEN') 8 | environment = os.getenv('ENVIRONMENT') 9 | pipeline_url = os.getenv('CI_PIPELINE_URL') 10 | pipeline_status = os.getenv('CI_JOB_STATUS') 11 | 12 | 13 | attachments_success = [ 14 | { 15 | "mrkdwn_in": ["text"], 16 | "color": "#36a64f", 17 | "pretext": 'Pipeline results have just arrived and they were awesome :star:', 18 | "title": f"Results for the {environment.upper()} environment", 19 | "text": f"Check out the pipeline <{pipeline_url}|here>.", 20 | "footer": "This is an automated notification. Check the `e2e` repo for more details.", 21 | } 22 | ] 23 | 24 | attachments_failure = [ 25 | { 26 | "mrkdwn_in": ["text"], 27 | "color": "#DC143C", 28 | "pretext": 'Pipeline results have just arrived and they were burning :fire:', 29 | "title": f"Results for the {environment.upper()} environment", 30 | "text": f"Check out the pipeline <{pipeline_url}|here>.", 31 | "footer": "This is an automated notification. Check the `e2e` repo for more details.", 32 | } 33 | ] 34 | 35 | try: 36 | attachments = attachments_success if pipeline_status == 'success' else attachments_failure 37 | client = WebClient(token=token) 38 | response = client.chat_postMessage(channel='', attachments=attachments) 39 | except SlackApiError as e: 40 | print(f"Got an error: {e.response['error']}") 41 | -------------------------------------------------------------------------------- /sonarqube/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM sonarqube:7.9-community 2 | COPY sonar.properties /opt/sonarqube/conf/ 3 | -------------------------------------------------------------------------------- /sonarqube/README.md: -------------------------------------------------------------------------------- 1 | ## Sonarqube 2 | Launch sonarqube with the command `$ docker-compose up -d`. It allows you to execute tasks such as static analysis, code coverage or even implement your code quality gate. 3 | 4 | > Example using [ui-tests](../ui-tests) 5 | ```shell script 6 | $ mvn -B clean verify sonar:sonar \ 7 | -Dskip.validate=true \ 8 | -Dmaven.test.skip=true \ 9 | -Dsonar.host.url=${SONARQUBE_ADDRESS} \ 10 | -Dsonar.qualitygate.wait=true \ 11 | -Dsonar.sources=src/test/java \ 12 | -Dsonar.tests=src/main/java \ 13 | -Dsonar.inclusions=src/test/java/**/*.java \ 14 | -Dsonar.tests.exclusions=src/test/java/**/*.java 15 | ``` -------------------------------------------------------------------------------- /sonarqube/docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: "3" 2 | 3 | services: 4 | 5 | sonarqube: 6 | container_name: sonarqube 7 | build: 8 | dockerfile: Dockerfile 9 | context: . 10 | ports: 11 | - "9000:9000" 12 | networks: 13 | - sonarnet 14 | environment: 15 | - sonar.jdbc.url=jdbc:postgresql://sonar-db:5432/sonar 16 | volumes: 17 | - sonarqube_conf:/opt/sonarqube/conf 18 | - sonarqube_data:/opt/sonarqube/data 19 | depends_on: 20 | - sonar-db 21 | 22 | sonar-db: 23 | image: postgres:9.6-alpine 24 | networks: 25 | - sonarnet 26 | environment: 27 | - POSTGRES_USER=sonar 28 | - POSTGRES_PASSWORD=sonar 29 | volumes: 30 | - postgresql:/var/lib/postgresql 31 | - postgresql_data:/var/lib/postgresql/data 32 | 33 | 34 | networks: 35 | sonarnet: 36 | driver: bridge 37 | 38 | volumes: 39 | sonarqube_conf: 40 | sonarqube_data: 41 | postgresql: 42 | postgresql_data: 43 | -------------------------------------------------------------------------------- /sonarqube/sonar.properties: -------------------------------------------------------------------------------- 1 | sonar.junit.reportPaths=target/surefire-reports 2 | sonar.coverage.jacoco.xmlReportPaths=target/site/jacoco/jacoco.xml 3 | -------------------------------------------------------------------------------- /ui-tests/README.md: -------------------------------------------------------------------------------- 1 | ## UI Tests 2 | Built with _java-maven_ using the goods of [Selenide](https://selenide.org/), [TestNG](https://testng.org/doc/), [ExtentReports](https://extentreports.com/), [Checkstyle](https://maven.apache.org/plugins/maven-checkstyle-plugin/), [Lombok](https://projectlombok.org/), and some others. Here you'll find the boilerplate code you need to have your ui testing framework up and ready in no time. 3 | 4 | ### POM - the Page Object Model 5 | 6 | The *ui-tests* uses the [Page Object Model](https://martinfowler.com/bliki/PageObject.html) to structure and organize its code. 7 | 8 | ![](../.docs/img/structure.gif) 9 | 10 | Within page objects you may find two kinds: 11 | 1. `Pages` complete page (eg. login page, home page) 12 | 1. `Components` reusable components within a page (eg. search bar, login form) 13 | 14 | > **NOTE:** components are not supposed to be restricted to single pages. Components are designed to be reused throughout the framework. 15 | > Thus, if you've to group them, group them by component type, not page; eg. forms, sidebars, modals. 16 | 17 | ### Suites 18 | You can have multiple suites under [/suites](src/test/resources/suites). And, in order to run any of them you can use a system property `-Dsuite=`. 19 | 20 | ```shell script 21 | $ mvn clean test -Dsuite= 22 | ``` 23 | 24 | > You can change the default suite on [pom.xml](pom.xml) properties. 25 | 26 | ### Parallel Test Execution 27 | You can run tests in parallel, configuring your suite file or with system properties. 28 | 29 | ```shell script 30 | $ mvn clean test -Dparallel= -Dthread.count= 31 | ``` 32 | 33 | ### Extent Reports 34 | Using [ExtentReports](http://www.extentreports.com/), you are able to automatically generate reports after test execution. These are stored under `reports/ExtentReport.html`. 35 | Furthermore, and by default, screenshots are taken upon test failure and attached to the report. 36 | 37 | > ⚠️ Requires the extent report listener property to be set. 38 | > 39 | > Example: `-Dlistener=${package}/utils/listeners/ExtentReportListener.java` 40 | 41 | ### Mocking Responses 42 | In order to mock http requests the framework uses browserup proxy behind selenide. This allows you to intercept, filter and manipulate requests and responses. 43 | 44 | ![](../.docs/img/mocked_response.png) 45 | 46 | First you've to model your request, so you can work with it anyhow you see fit. 47 | Therefore, in order to create a new object to model a mocked request (eg. `ExampleMockModel.java`) it has to implement [MockDefinition](src/test/java/io/company/utils/mocks/MockDefinition.java) interface. 48 | 49 | ````java 50 | public class ExampleMockModel implements MockDefinition { ... } 51 | ```` 52 | 53 | Then, use the [@Mock](src/test/java/io/company/utils/mocks/Mock.java) annotation in order to apply it for a given test case. 54 | 55 | ##### Snippet 56 | ```java 57 | @Target(ElementType.METHOD) 58 | @Retention(RetentionPolicy.RUNTIME) 59 | public @interface Mock { 60 | Class[] clazz(); 61 | } 62 | ``` 63 | 64 | The annotation may be declared for methods or class types. 65 | 66 | ````java 67 | @Test 68 | @Mock(clazz = {ExampleMockModel.class, OtherExampleMockModel.class}) 69 | public void exampleMockedTest() { ... } 70 | ```` 71 | 72 | ⚠️ For this to work you have to enable the proxy and use the [MockListener](src/test/java/io/company/utils/listeners/MockListener.java) class. 73 | 74 | ```shell script 75 | $ mvn clean test -Dselenide.proxyEnabled=true -Dlistener=${package}/utils/listeners/MockListener.java 76 | ``` 77 | 78 | > **NOTE:** safari does not support this use case. 79 | 80 | ### Checkstyle 81 | This feature integrates your project with a code linter, so that everyone follows the same code style within the team. 82 | 83 | ```shell script 84 | $ mvn validate 85 | ``` -------------------------------------------------------------------------------- /ui-tests/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 4.0.0 6 | 7 | awesome.group.id 8 | test-automation-bootstrap 9 | 1.0.0-SNAPSHOT 10 | jar 11 | 12 | test-automation-bootstrap 13 | A template to jumpstart test automation frameworks 14 | https://github.com/sergiomartins8/test-automation-bootstrap 15 | 16 | 17 | UTF-8 18 | UTF-8 19 | 11 20 | 21 | 22 | suiteA 23 | 24 | 25 | 26 | 27 | 5.17.2 28 | 7.5.1 29 | 2.22.2 30 | 3.8.1 31 | 3.1.1 32 | 6.4 33 | 1.2.13 34 | 4.0.6 35 | 2.1.1 36 | 2.11.1 37 | 1.18.16 38 | 39 | 40 | 41 | 42 | 43 | org.apache.maven.plugins 44 | maven-compiler-plugin 45 | ${maven.compiler.plugin} 46 | 47 | ${java.version} 48 | ${java.version} 49 | 50 | 51 | 52 | org.apache.maven.plugins 53 | maven-surefire-plugin 54 | ${maven.surefire.plugin} 55 | 56 | 57 | 58 | parallel 59 | ${parallel} 60 | 61 | 62 | threadCount 63 | ${thread.count} 64 | 65 | 66 | listener 67 | ${listener} 68 | 69 | 70 | 71 | src/test/resources/suites/${suite}.xml 72 | 73 | 74 | 75 | 76 | org.apache.maven.plugins 77 | maven-checkstyle-plugin 78 | ${maven.checkstyle.plugin} 79 | 80 | src/test/resources/checkstyle/checkstyle.xml 81 | 82 | 83 | 84 | validate 85 | validate 86 | 87 | UTF-8 88 | true 89 | true 90 | true 91 | 92 | 93 | check 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | net.logstash.logback 104 | logstash-logback-encoder 105 | ${logstash.logback.encoder} 106 | 107 | 108 | ch.qos.logback 109 | logback-classic 110 | ${logback.classic} 111 | 112 | 113 | com.codeborne 114 | selenide 115 | ${selenide.version} 116 | 117 | 118 | org.testng 119 | testng 120 | ${testng.version} 121 | 122 | 123 | com.aventstack 124 | extentreports 125 | ${extent.reports} 126 | 127 | 128 | com.browserup 129 | browserup-proxy-core 130 | ${browserup.proxy.core} 131 | 132 | 133 | com.fasterxml.jackson.core 134 | jackson-annotations 135 | ${jackson.annotations} 136 | 137 | 138 | org.projectlombok 139 | lombok 140 | true 141 | ${lombok.version} 142 | 143 | 144 | 145 | -------------------------------------------------------------------------------- /ui-tests/src/main/java/io/company/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sergiomartins8/test-automation-bootstrap/bf93b17702e71fc9589a3318870515b2299f66ba/ui-tests/src/main/java/io/company/.gitkeep -------------------------------------------------------------------------------- /ui-tests/src/main/resources/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sergiomartins8/test-automation-bootstrap/bf93b17702e71fc9589a3318870515b2299f66ba/ui-tests/src/main/resources/.gitkeep -------------------------------------------------------------------------------- /ui-tests/src/test/java/io/company/pageobjects/PageObject.java: -------------------------------------------------------------------------------- 1 | package io.company.pageobjects; 2 | 3 | import io.company.utils.logging.Loggable; 4 | 5 | /** 6 | * Abstraction that represents the definition of a page object. 7 | *
8 | * Contains methods that are reusable by any page object 9 | * 10 | * @param represents the type of the concrete page object 11 | */ 12 | public abstract class PageObject> implements Loggable { 13 | } 14 | -------------------------------------------------------------------------------- /ui-tests/src/test/java/io/company/pageobjects/components/BaseComponent.java: -------------------------------------------------------------------------------- 1 | package io.company.pageobjects.components; 2 | 3 | import com.codeborne.selenide.SelenideElement; 4 | import io.company.pageobjects.PageObject; 5 | 6 | import static com.codeborne.selenide.Selenide.$; 7 | 8 | /** 9 | * Base abstraction for concrete types of components. 10 | * 11 | * @param represents the type of the concrete component. 12 | * Example: {@code public class ConcreteComponent extends BaseComponent} 13 | */ 14 | public abstract class BaseComponent> extends PageObject { 15 | 16 | /** 17 | * Unique element that describes this component. 18 | */ 19 | private final SelenideElement element; 20 | 21 | public BaseComponent(String selector) { 22 | element = $(selector); 23 | } 24 | 25 | public SelenideElement self() { 26 | return element; 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /ui-tests/src/test/java/io/company/pageobjects/components/SearchComponent.java: -------------------------------------------------------------------------------- 1 | package io.company.pageobjects.components; 2 | 3 | import io.company.pageobjects.pages.GoogleResultsPage; 4 | import org.openqa.selenium.Keys; 5 | 6 | public class SearchComponent extends BaseComponent { 7 | public SearchComponent(String selector) { 8 | super(selector); 9 | } 10 | 11 | public GoogleResultsPage searchFor(String word) { 12 | self().sendKeys(word, Keys.ENTER); 13 | return new GoogleResultsPage(); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /ui-tests/src/test/java/io/company/pageobjects/pages/BasePage.java: -------------------------------------------------------------------------------- 1 | package io.company.pageobjects.pages; 2 | 3 | import io.company.pageobjects.PageObject; 4 | 5 | /** 6 | * Base abstraction for concrete types of pages. 7 | * 8 | * @param represents the type of the concrete page. 9 | * Example: {@code public class ConcretePage extends BasePage} 10 | */ 11 | public class BasePage> extends PageObject { 12 | } 13 | -------------------------------------------------------------------------------- /ui-tests/src/test/java/io/company/pageobjects/pages/GoogleResultsPage.java: -------------------------------------------------------------------------------- 1 | package io.company.pageobjects.pages; 2 | 3 | import com.codeborne.selenide.ElementsCollection; 4 | import io.company.pageobjects.components.SearchComponent; 5 | 6 | import static com.codeborne.selenide.Selenide.$$; 7 | 8 | public class GoogleResultsPage extends BasePage { 9 | 10 | /** 11 | * Google search input text selector. 12 | */ 13 | private static final String SEARCH_SELECTOR = "[name='q']"; 14 | private static final String SEARCH_RESULTS = "#res .g"; 15 | 16 | private final SearchComponent searchComponent; 17 | 18 | public GoogleResultsPage() { 19 | searchComponent = new SearchComponent(SEARCH_SELECTOR); 20 | } 21 | 22 | public SearchComponent searchComponent() { 23 | return searchComponent; 24 | } 25 | 26 | public ElementsCollection getResults() { 27 | return $$(SEARCH_RESULTS); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /ui-tests/src/test/java/io/company/pageobjects/pages/GoogleSearchPage.java: -------------------------------------------------------------------------------- 1 | package io.company.pageobjects.pages; 2 | 3 | import io.company.pageobjects.components.SearchComponent; 4 | 5 | public class GoogleSearchPage extends BasePage { 6 | 7 | /** 8 | * Google search input text selector. 9 | */ 10 | private static final String SEARCH_SELECTOR = "[name='q']"; 11 | 12 | private final SearchComponent searchComponent; 13 | 14 | public GoogleSearchPage() { 15 | searchComponent = new SearchComponent(SEARCH_SELECTOR); 16 | } 17 | 18 | public SearchComponent searchComponent() { 19 | return searchComponent; 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /ui-tests/src/test/java/io/company/tests/Base.java: -------------------------------------------------------------------------------- 1 | package io.company.tests; 2 | 3 | import io.company.utils.logging.Loggable; 4 | import org.testng.annotations.AfterSuite; 5 | import org.testng.annotations.BeforeSuite; 6 | 7 | /** 8 | * Where it all starts. 9 | *
10 | * The framework is initialized here with all the required configurations. 11 | */ 12 | public abstract class Base implements Loggable { 13 | 14 | @BeforeSuite 15 | public void initializeGlobalConfiguration() { 16 | logger().info("Initialize global configuration"); 17 | } 18 | 19 | @AfterSuite 20 | public void teardownGlobalConfiguration() { 21 | logger().info("Teardown global configuration"); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /ui-tests/src/test/java/io/company/tests/BaseTest.java: -------------------------------------------------------------------------------- 1 | package io.company.tests; 2 | 3 | import com.codeborne.selenide.WebDriverRunner; 4 | import io.company.utils.api.auth.AuthApi; 5 | import io.company.utils.api.auth.model.Authentication; 6 | 7 | import static com.codeborne.selenide.Selenide.localStorage; 8 | import static com.codeborne.selenide.Selenide.open; 9 | 10 | /** 11 | * Base abstraction for concrete types of tests. 12 | * Holds the implementation of methods to be reusable across tests. 13 | *
14 | * Example: login methods, cookies, username and password credentials 15 | */ 16 | public abstract class BaseTest extends Base { 17 | 18 | /** 19 | * This method allows to bypass the login process, injecting the necessary tokens in the browser local storage or cookies, 20 | * adapt accordingly. 21 | *

22 | * The login bypass — A better approach to automated regression testing 23 | * https://medium.com/@sergiomartins8/the-login-bypass-a-better-approach-to-automated-regression-testing-f3c23f32d7b8 24 | *

25 | *

26 | * USAGE EXAMPLE: 27 | * ProfilePage profilePage = bypassLogin(User.EMAIL, User.PASSWORD, Urls.PROFILE, ProfilePage.class); 28 | *

29 | * 30 | * @param email user email 31 | * @param password user password 32 | * @param url page url 33 | * @param clazz page object 34 | * @return page object that represents the current page 35 | */ 36 | public T bypassLogin(String email, String password, String url, Class clazz) { 37 | // Collaborate with your team in order to find out what you need to implement the code below. 38 | // This should authenticate the user and return his authentication tokens. 39 | Authentication authentication = AuthApi.getAuthentication(email, password); 40 | 41 | // Make sure you have the browser opened before setting cookies, otherwise you'll get an exception. 42 | if (!WebDriverRunner.hasWebDriverStarted()) { 43 | open(url); 44 | } 45 | 46 | setAuthenticationLocalStorage(authentication); 47 | return open(url, clazz); 48 | } 49 | 50 | /** 51 | * Set authentication tokens on browser local storage. 52 | * 53 | * @param authentication authentication object containing tokens 54 | */ 55 | private void setAuthenticationLocalStorage(Authentication authentication) { 56 | localStorage().setItem("accessToken", authentication.getAccessToken()); 57 | localStorage().setItem("refreshToken", authentication.getRefreshToken()); 58 | logger().info("Authentication tokens are set"); 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /ui-tests/src/test/java/io/company/tests/examples/ExampleMockedTest.java: -------------------------------------------------------------------------------- 1 | package io.company.tests.examples; 2 | 3 | import io.company.tests.BaseTest; 4 | import io.company.utils.mocks.Mock; 5 | import io.company.utils.mocks.model.MockExampleModel; 6 | import org.testng.annotations.BeforeMethod; 7 | import org.testng.annotations.Test; 8 | 9 | import static com.codeborne.selenide.Selenide.open; 10 | 11 | /** 12 | * Use @Listeners(...) in order to execute the tests with mocked responses from the @Mock annotation "clazz". 13 | *
14 | * On the other hand, simply execute the tests by passing the listener maven property (check the documentation). 15 | **/ 16 | //@Listeners(MockListener.class) 17 | public class ExampleMockedTest extends BaseTest { 18 | 19 | @BeforeMethod 20 | public void setup() { 21 | open("http://www.google.pt"); 22 | } 23 | 24 | @Mock(clazz = MockExampleModel.class) 25 | @Test(description = "Test based on mock expectations") 26 | public void shouldUserBeLoggedIn() { 27 | logger().info("Performing a mocked request"); 28 | open("https://www.google.pt/mock"); 29 | logger().warn("Received mocked response"); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /ui-tests/src/test/java/io/company/tests/examples/GoogleSearchTest.java: -------------------------------------------------------------------------------- 1 | package io.company.tests.examples; 2 | 3 | import io.company.pageobjects.pages.GoogleResultsPage; 4 | import io.company.pageobjects.pages.GoogleSearchPage; 5 | import io.company.tests.BaseTest; 6 | import org.testng.annotations.AfterMethod; 7 | import org.testng.annotations.BeforeMethod; 8 | import org.testng.annotations.Test; 9 | 10 | import static com.codeborne.selenide.CollectionCondition.sizeGreaterThan; 11 | import static com.codeborne.selenide.Condition.value; 12 | import static com.codeborne.selenide.Selenide.open; 13 | 14 | public class GoogleSearchTest extends BaseTest { 15 | private GoogleSearchPage googleSearchPage; 16 | private GoogleResultsPage googleResultsPage; 17 | 18 | /** 19 | * Initialize private objects. 20 | */ 21 | @BeforeMethod 22 | public void setup() { 23 | logger().debug("Setup test"); 24 | googleSearchPage = new GoogleSearchPage(); 25 | googleResultsPage = new GoogleResultsPage(); 26 | } 27 | 28 | @AfterMethod 29 | public void teardown() { 30 | logger().debug("Teardown test"); 31 | } 32 | 33 | @Test(description = "Open up a google page and search for the word 'dogs'") 34 | public void shouldPerformSearch() { 35 | open("https://google.com"); 36 | 37 | googleSearchPage 38 | .searchComponent() 39 | .searchFor("dogs") 40 | .searchComponent() 41 | .self() 42 | .shouldHave(value("dogs")); 43 | } 44 | 45 | @Test 46 | public void shouldSearchResultsBeDisplayed() { 47 | open("https://www.google.pt/search?source=hp&q=dogs&oq=dogs"); 48 | 49 | googleResultsPage 50 | .getResults() 51 | .shouldHave(sizeGreaterThan(1)); 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /ui-tests/src/test/java/io/company/utils/api/ClientApi.java: -------------------------------------------------------------------------------- 1 | package io.company.utils.api; 2 | 3 | import com.fasterxml.jackson.databind.JsonNode; 4 | import com.fasterxml.jackson.databind.ObjectMapper; 5 | import lombok.SneakyThrows; 6 | import okhttp3.MediaType; 7 | import okhttp3.OkHttpClient; 8 | import okhttp3.Request; 9 | import okhttp3.RequestBody; 10 | import okhttp3.Response; 11 | import okio.Buffer; 12 | import org.apache.http.client.HttpResponseException; 13 | import org.slf4j.Logger; 14 | import org.slf4j.LoggerFactory; 15 | 16 | import static java.util.Objects.requireNonNull; 17 | 18 | /** 19 | * Abstract class to be extended by other API model classes. 20 | *
21 | * Provides the singleton http client and other utility constants. 22 | * Also, contains the implementation of a generic response handler in order to avoid code duplication and promote re-usability. 23 | */ 24 | public abstract class ClientApi { 25 | private static final Logger LOGGER = LoggerFactory.getLogger(ClientApi.class); 26 | 27 | protected static final OkHttpClient OK_HTTP_CLIENT = new OkHttpClient(); 28 | protected static final MediaType JSON = MediaType.parse("application/json"); 29 | protected static final ObjectMapper OBJECT_MAPPER = new ObjectMapper(); 30 | 31 | protected static final String HEADER_AUTHORIZATION = "authorization"; 32 | 33 | /** 34 | * Generic response handler. 35 | * Handles responses of any http requests. 36 | * 37 | * @param request object with the request to be performed 38 | * @param clazz object to be returned 39 | * @param class type 40 | * @return concrete object 41 | */ 42 | @SneakyThrows 43 | public static T responseHandler(Request request, Class clazz) { 44 | Response response = OK_HTTP_CLIENT.newCall(request).execute(); 45 | 46 | if (!response.isSuccessful()) { 47 | LOGGER.error("Failed for request: {}", request); 48 | LOGGER.error("Failed with request body: {}", bodyToString(requireNonNull(request.body()))); 49 | LOGGER.error("Failed request with response body: {}", requireNonNull(response.body()).string()); 50 | throw new HttpResponseException(response.code(), "Response was unsuccessful"); 51 | } 52 | 53 | JsonNode node = OBJECT_MAPPER.readTree(requireNonNull(response.body()).string()); 54 | response.close(); 55 | OK_HTTP_CLIENT.connectionPool().evictAll(); 56 | 57 | return OBJECT_MAPPER.treeToValue(node, clazz); 58 | } 59 | 60 | /** 61 | * Converts a request body into a string. 62 | * 63 | * @param request request body to be converted 64 | * @return request body as a string 65 | */ 66 | @SneakyThrows 67 | private static String bodyToString(RequestBody request) { 68 | final Buffer buffer = new Buffer(); 69 | request.writeTo(buffer); 70 | return buffer.readUtf8(); 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /ui-tests/src/test/java/io/company/utils/api/auth/AuthApi.java: -------------------------------------------------------------------------------- 1 | package io.company.utils.api.auth; 2 | 3 | import io.company.utils.api.ClientApi; 4 | import io.company.utils.api.auth.model.Authentication; 5 | 6 | /** 7 | * Example of how to model your authentication API to get the desired tokens to use other APIs. 8 | *
9 | * Customize and model this API according to your needs. 10 | */ 11 | public class AuthApi extends ClientApi { 12 | 13 | /** 14 | * Used to get authentication tokens in order to perform authorized requests. 15 | * 16 | * @param email user email 17 | * @param password user password 18 | * @return {@link Authentication} object 19 | */ 20 | public static Authentication getAuthentication(String email, String password) { 21 | // 1. get authentication tokens 22 | 23 | // your code here 24 | 25 | // 2. build your authentication object 26 | Authentication authentication = new Authentication(); 27 | authentication.setAccessToken("YourAccessToken"); 28 | authentication.setRefreshToken("YourRefreshToken"); 29 | 30 | // 3. return authentication object 31 | return authentication; 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /ui-tests/src/test/java/io/company/utils/api/auth/model/Authentication.java: -------------------------------------------------------------------------------- 1 | package io.company.utils.api.auth.model; 2 | 3 | import com.fasterxml.jackson.annotation.JsonIgnoreProperties; 4 | import com.fasterxml.jackson.annotation.JsonInclude; 5 | import com.fasterxml.jackson.annotation.JsonProperty; 6 | import lombok.Getter; 7 | import lombok.Setter; 8 | 9 | /** 10 | * Note that this uses lombok dependencies. 11 | * Thus, for this to work (at least on intellij) you have to download and install lombok plugin and enable annotation processing. 12 | * https://github.com/mplushnikov/lombok-intellij-plugin 13 | */ 14 | @Getter 15 | @Setter 16 | @JsonInclude(JsonInclude.Include.NON_NULL) 17 | @JsonIgnoreProperties(ignoreUnknown = true) 18 | public class Authentication { 19 | @JsonProperty("AccessToken") 20 | private String accessToken; 21 | 22 | @JsonProperty("RefreshToken") 23 | private String refreshToken; 24 | } 25 | -------------------------------------------------------------------------------- /ui-tests/src/test/java/io/company/utils/api/user/UserApi.java: -------------------------------------------------------------------------------- 1 | package io.company.utils.api.user; 2 | 3 | import io.company.utils.api.ClientApi; 4 | import io.company.utils.api.auth.model.Authentication; 5 | import io.company.utils.api.user.model.User; 6 | import io.company.utils.constants.Urls; 7 | import okhttp3.Request; 8 | import okhttp3.RequestBody; 9 | import org.slf4j.Logger; 10 | import org.slf4j.LoggerFactory; 11 | 12 | import java.io.IOException; 13 | 14 | /** 15 | * Example model of an API to get info from users. 16 | * Contains methods such as post, get and patch. 17 | *
18 | * Customize and model this API according to your needs. 19 | */ 20 | public class UserApi extends ClientApi { 21 | private static final Logger LOGGER = LoggerFactory.getLogger(UserApi.class); 22 | 23 | /** 24 | * Method to create user. 25 | * 26 | * @param url api base url 27 | * @param authentication user authentication 28 | * @param user user to be created 29 | * @return the created user 30 | */ 31 | public static User post(String url, Authentication authentication, User user) { 32 | try { 33 | RequestBody requestBody = RequestBody.create(JSON, OBJECT_MAPPER.writeValueAsString(user)); 34 | 35 | Request request = new Request.Builder() 36 | .url(url + Urls.Api.USERS) 37 | .addHeader(HEADER_AUTHORIZATION, authentication.getAccessToken()) 38 | .post(requestBody) 39 | .build(); 40 | 41 | return responseHandler(request, User.class); 42 | } catch (IOException e) { 43 | LOGGER.error(e.getMessage()); 44 | } 45 | return null; 46 | } 47 | 48 | /** 49 | * Method to list all users. 50 | * 51 | * @param url api base url 52 | * @param authentication user authentication 53 | * @return the list of users 54 | */ 55 | public static User[] get(String url, Authentication authentication) { 56 | Request request = new Request.Builder() 57 | .url(url + Urls.Api.USERS) 58 | .addHeader(HEADER_AUTHORIZATION, authentication.getAccessToken()) 59 | .get() 60 | .build(); 61 | 62 | return responseHandler(request, User[].class); 63 | } 64 | 65 | /** 66 | * Method to get the details from a single user. 67 | * 68 | * @param url api base url 69 | * @param authentication user authentication 70 | * @param id user id 71 | * @return the user details 72 | */ 73 | public static User get(String url, Authentication authentication, int id) { 74 | Request request = new Request.Builder() 75 | .url(url + Urls.Api.USERS + id) 76 | .addHeader(HEADER_AUTHORIZATION, authentication.getAccessToken()) 77 | .get() 78 | .build(); 79 | 80 | return responseHandler(request, User.class); 81 | } 82 | 83 | /** 84 | * Method to update user details. 85 | * 86 | * @param url api base url 87 | * @param authentication user authentication 88 | * @param user user details to be updated 89 | * @return the updated user 90 | */ 91 | public static User patch(String url, Authentication authentication, User user) { 92 | try { 93 | RequestBody requestBody = RequestBody.create(JSON, OBJECT_MAPPER.writeValueAsString(user)); 94 | 95 | Request request = new Request.Builder() 96 | .url(url + Urls.Api.USERS + user.getId()) 97 | .addHeader(HEADER_AUTHORIZATION, authentication.getAccessToken()) 98 | .patch(requestBody) 99 | .build(); 100 | 101 | return responseHandler(request, User.class); 102 | } catch (IOException e) { 103 | LOGGER.error(e.getMessage()); 104 | } 105 | return null; 106 | } 107 | } 108 | -------------------------------------------------------------------------------- /ui-tests/src/test/java/io/company/utils/api/user/model/User.java: -------------------------------------------------------------------------------- 1 | package io.company.utils.api.user.model; 2 | 3 | import com.fasterxml.jackson.annotation.JsonIgnoreProperties; 4 | import com.fasterxml.jackson.annotation.JsonInclude; 5 | import com.fasterxml.jackson.annotation.JsonProperty; 6 | import lombok.Getter; 7 | import lombok.Setter; 8 | 9 | /** 10 | * Note that this uses lombok dependencies. 11 | * Thus, for this to work (at least on intellij) you have to download and install lombok plugin and enable annotation processing. 12 | * https://github.com/mplushnikov/lombok-intellij-plugin 13 | */ 14 | @Getter 15 | @Setter 16 | @JsonInclude(JsonInclude.Include.NON_NULL) 17 | @JsonIgnoreProperties(ignoreUnknown = true) 18 | public class User { 19 | @JsonProperty("id") 20 | private Integer id; 21 | 22 | @JsonProperty("name") 23 | private String name; 24 | 25 | @JsonProperty("email") 26 | private String email; 27 | } 28 | -------------------------------------------------------------------------------- /ui-tests/src/test/java/io/company/utils/config/CustomConfiguration.java: -------------------------------------------------------------------------------- 1 | package io.company.utils.config; 2 | 3 | /** 4 | * Custom configuration settings. 5 | *
6 | * This class is designed so that every setting can be set either via system property or programmatically 7 | *
8 | * Please note that all fields are static, meaning that every change 9 | * will immediately reflect in all threads (if you run tests in parallel) 10 | */ 11 | public class CustomConfiguration { 12 | 13 | /** 14 | * Private constructor to avoid instantiation. 15 | */ 16 | private CustomConfiguration() { 17 | } 18 | 19 | /** 20 | * Default configuration settings holder. 21 | */ 22 | private static final CustomConfigurationHolder DEFAULTS = new CustomConfigurationHolder(); 23 | 24 | /** 25 | * Custom configuration example. 26 | * Can be configured either programmatically or by system property: {@code -Dcustom.configuration=somethingYouWant} 27 | *
28 | * Default value: null (Custom configuration is not set) 29 | */ 30 | public static String exampleConfiguration = DEFAULTS.exampleConfiguration(); 31 | 32 | /** 33 | * The pipeline ID for the current execution. 34 | * Can be configured either programmatically or by environment variable. 35 | *
36 | * EXAMPLE: "export CI_PIPELINE_IID=9000" 37 | * abr> 38 | * Default value: null (environment variable not defined). 39 | */ 40 | public static int pipelineID = DEFAULTS.pipelineID(); 41 | } 42 | -------------------------------------------------------------------------------- /ui-tests/src/test/java/io/company/utils/config/CustomConfigurationHolder.java: -------------------------------------------------------------------------------- 1 | package io.company.utils.config; 2 | 3 | import io.company.utils.logging.Loggable; 4 | 5 | public class CustomConfigurationHolder implements Loggable { 6 | private final String customConfiguration = System.getProperty("custom.configuration"); 7 | private final int pipelineID = System.getenv("CI_PIPELINE_ID") == null 8 | ? -1 : Integer.parseInt(System.getenv("CI_PIPELINE_ID")); 9 | 10 | public String exampleConfiguration() { 11 | logger().info("Custom config: " + customConfiguration); 12 | return customConfiguration; 13 | } 14 | 15 | public int pipelineID() { 16 | return pipelineID; 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /ui-tests/src/test/java/io/company/utils/constants/Constants.java: -------------------------------------------------------------------------------- 1 | package io.company.utils.constants; 2 | 3 | /** 4 | * Global constants to be used framework wide. 5 | */ 6 | public class Constants { 7 | 8 | /** 9 | * Private constructor to avoid instantiation. 10 | */ 11 | private Constants() { 12 | } 13 | 14 | public static final long EXAMPLE_CONSTANT = 123; 15 | } 16 | -------------------------------------------------------------------------------- /ui-tests/src/test/java/io/company/utils/constants/Urls.java: -------------------------------------------------------------------------------- 1 | package io.company.utils.constants; 2 | 3 | /** 4 | * Utility class that holds url constants. 5 | */ 6 | public class Urls { 7 | 8 | /** 9 | * Private constructor to avoid instantiation. 10 | */ 11 | private Urls() { 12 | } 13 | 14 | /** 15 | * Page related. 16 | */ 17 | public static class Page { 18 | 19 | /** 20 | * Private constructor to avoid instantiation. 21 | */ 22 | private Page() { 23 | } 24 | 25 | public static final String HOME = "home/"; 26 | public static final String MY_ACCOUNT = "my-account/"; 27 | } 28 | 29 | /** 30 | * Api related. 31 | */ 32 | public static class Api { 33 | 34 | /** 35 | * Private constructor to avoid instantiation. 36 | */ 37 | private Api() { 38 | } 39 | 40 | public static final String BASE_URL_DEV = "https://your-awesome-api-dev-base-url/"; 41 | public static final String BASE_URL_PROD = "https://your-awesome-api-prod-base-url/"; 42 | 43 | public static final String USERS = "users/"; 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /ui-tests/src/test/java/io/company/utils/listeners/DistributedReportListener.java: -------------------------------------------------------------------------------- 1 | package io.company.utils.listeners; 2 | 3 | import com.codeborne.selenide.Configuration; 4 | import io.company.utils.config.CustomConfiguration; 5 | import io.company.utils.logging.Loggable; 6 | import org.testng.ITestListener; 7 | import org.testng.ITestResult; 8 | 9 | public class DistributedReportListener implements ITestListener, Loggable { 10 | 11 | @Override 12 | public void onTestSuccess(ITestResult iTestResult) { 13 | logger().info("Pipeline: {} Browser: {} Status: {} Test: {} Suite: {}", 14 | CustomConfiguration.pipelineID, Configuration.browser, "passed", iTestResult.getName(), System.getProperty("suite")); 15 | } 16 | 17 | @Override 18 | public void onTestFailure(ITestResult iTestResult) { 19 | logger().error("Pipeline: {} Browser: {} Status: {} Test: {} Suite: {} Trace: {}", 20 | CustomConfiguration.pipelineID, Configuration.browser, "failed", iTestResult.getName(), System.getProperty("suite"), 21 | iTestResult.getThrowable().getMessage()); 22 | } 23 | 24 | @Override 25 | public void onTestSkipped(ITestResult iTestResult) { 26 | logger().info("Pipeline: {} Browser: {} Status: {} Test: {} Suite: {}", 27 | CustomConfiguration.pipelineID, Configuration.browser, "skipped", iTestResult.getName(), System.getProperty("suite")); 28 | } 29 | } -------------------------------------------------------------------------------- /ui-tests/src/test/java/io/company/utils/listeners/ExtentReportListener.java: -------------------------------------------------------------------------------- 1 | package io.company.utils.listeners; 2 | 3 | import com.aventstack.extentreports.MediaEntityBuilder; 4 | import com.aventstack.extentreports.markuputils.ExtentColor; 5 | import com.aventstack.extentreports.markuputils.MarkupHelper; 6 | import io.company.utils.logging.Loggable; 7 | import io.company.utils.reports.ExtentManager; 8 | import io.company.utils.reports.ExtentTestReport; 9 | import org.openqa.selenium.OutputType; 10 | import org.openqa.selenium.TakesScreenshot; 11 | import org.testng.ITestContext; 12 | import org.testng.ITestListener; 13 | import org.testng.ITestResult; 14 | 15 | import java.io.IOException; 16 | 17 | import static com.codeborne.selenide.WebDriverRunner.getWebDriver; 18 | 19 | /** 20 | * {@link ITestListener} implementation responsible for the test result reports. 21 | */ 22 | public class ExtentReportListener implements ITestListener, Loggable { 23 | 24 | @Override 25 | public void onFinish(ITestContext iTestContext) { 26 | logger().debug(iTestContext.getName() + " finished"); 27 | ExtentManager.getInstance().flush(); 28 | ExtentTestReport.removeTest(); 29 | } 30 | 31 | @Override 32 | public void onTestStart(ITestResult iTestResult) { 33 | logger().debug("Test {} starting", iTestResult.getName()); 34 | ExtentTestReport.createTest(iTestResult.getInstanceName(), iTestResult.getName()); 35 | } 36 | 37 | @Override 38 | public void onTestSuccess(ITestResult iTestResult) { 39 | logger().debug("Test {} passed", iTestResult.getName()); 40 | ExtentTestReport.getTest().pass("Test passed"); 41 | } 42 | 43 | @Override 44 | public void onTestFailure(ITestResult iTestResult) { 45 | logger().error("Test {} failed", iTestResult.getName()); 46 | 47 | ExtentTestReport.getTest().fail(MarkupHelper.createLabel(iTestResult.getThrowable().getMessage(), ExtentColor.RED)); 48 | 49 | try { 50 | ExtentTestReport 51 | .getTest() 52 | .fail("Screenshot - ", MediaEntityBuilder 53 | .createScreenCaptureFromBase64String(getScreenshotBase64()) 54 | .build()); 55 | } catch (IOException e) { 56 | logger().error(e.getMessage()); 57 | } 58 | } 59 | 60 | @Override 61 | public void onTestSkipped(ITestResult iTestResult) { 62 | logger().debug("Test {} skipped", iTestResult.getName()); 63 | ExtentTestReport.getTest().skip("Test Skipped"); 64 | } 65 | 66 | private String getScreenshotBase64() { 67 | return "data:image/png;base64," + ((TakesScreenshot) getWebDriver()) 68 | .getScreenshotAs(OutputType.BASE64); 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /ui-tests/src/test/java/io/company/utils/listeners/MockListener.java: -------------------------------------------------------------------------------- 1 | package io.company.utils.listeners; 2 | 3 | import com.codeborne.selenide.WebDriverRunner; 4 | import io.company.utils.logging.Loggable; 5 | import io.company.utils.mocks.Mock; 6 | import io.company.utils.mocks.MockDefinition; 7 | import org.testng.ITestListener; 8 | import org.testng.ITestResult; 9 | 10 | import java.lang.reflect.InvocationTargetException; 11 | import java.util.Optional; 12 | import java.util.regex.Pattern; 13 | 14 | /** 15 | * {@link ITestListener} implementation responsible for mocked data to be used during runtime, for a single test. 16 | */ 17 | public class MockListener implements ITestListener, Loggable { 18 | 19 | /** 20 | * TL;DR: 21 | * For this to work, we're using the selenide proxy implementation. Thus, allowing us to intercept and swap any http responses. 22 | *
23 | * This piece of logic extracts the clazz field within the {@link Mock} annotation, containing the array of model objects to be mocked. 24 | * Then, for each model, instantiates it back into a {@link MockDefinition} object. 25 | *
26 | * Having the object, we use it to add its fields to the http response - through a filter. 27 | * 28 | * @param result the current test result. 29 | */ 30 | @Override 31 | public void onTestStart(ITestResult result) { 32 | extractMockAnnotation(result).ifPresent(mockAnnotation -> { 33 | try { 34 | for (Class mockedModel : mockAnnotation.clazz()) { 35 | MockDefinition mockDefinition = mockedModel.getDeclaredConstructor().newInstance(); 36 | 37 | logger().info("Adding response filter from model '{}.class'", mockedModel.getSimpleName()); 38 | logger().info("Adding response filter '{}'", mockedModel.getSimpleName().toLowerCase()); 39 | 40 | WebDriverRunner 41 | .getSelenideProxy() 42 | .addResponseFilter(mockedModel.getSimpleName().toLowerCase(), (response, contents, messageInfo) -> { 43 | if (messageInfo.getOriginalRequest().method().equals(mockDefinition.methodName()) 44 | && Pattern.compile(mockDefinition.urlPattern()).matcher(messageInfo.getOriginalUrl()).matches()) { 45 | logger().info("Original url request: {} {}", mockDefinition.methodName(), mockDefinition.urlPattern()); 46 | logger().info("Matching pattern: {}", mockDefinition.urlPattern()); 47 | 48 | logger().info("Mocking content body: {}", mockDefinition.contentBody()); 49 | contents.setTextContents(mockDefinition.contentBody()); 50 | 51 | logger().info("Mocking response status: {}", mockDefinition.responseStatus()); 52 | response.setStatus(mockDefinition.responseStatus()); 53 | 54 | logger().info("Mocking response headers: {}", mockDefinition.headers()); 55 | response.headers().add(mockDefinition.headers()); 56 | } 57 | }); 58 | } 59 | } catch (InstantiationException | IllegalAccessException | InvocationTargetException | NoSuchMethodException e) { 60 | logger().error(e.getMessage()); 61 | } 62 | }); 63 | } 64 | 65 | @Override 66 | public void onTestSuccess(ITestResult result) { 67 | extractMockAnnotation(result).ifPresent(mock -> 68 | WebDriverRunner.getWebDriver().quit()); 69 | } 70 | 71 | @Override 72 | public void onTestFailure(ITestResult result) { 73 | extractMockAnnotation(result).ifPresent(mock -> 74 | WebDriverRunner.getWebDriver().quit()); 75 | } 76 | 77 | @Override 78 | public void onTestSkipped(ITestResult result) { 79 | extractMockAnnotation(result).ifPresent(mock -> 80 | WebDriverRunner.getWebDriver().quit()); 81 | } 82 | 83 | private Optional extractMockAnnotation(ITestResult result) { 84 | return Optional.ofNullable(result.getMethod() 85 | .getConstructorOrMethod() 86 | .getMethod() 87 | .getAnnotation(Mock.class)); 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /ui-tests/src/test/java/io/company/utils/logging/AsJson.java: -------------------------------------------------------------------------------- 1 | package io.company.utils.logging; 2 | 3 | import com.fasterxml.jackson.databind.ObjectMapper; 4 | import lombok.SneakyThrows; 5 | 6 | /** 7 | * Allows whoever implements {@link AsJson} interface to be printed as a json string. 8 | *
9 | * Pretty useful for logging model/POJO classes. 10 | *
11 | * Example: 12 | *

13 | * public class UserModel implements AsJson {} 14 | *

15 | *

16 | * public void printObjectAsJson() { 17 | * UserModel userModel = new UserModel(); 18 | * System.out.println(userModel.toJson()); 19 | * } 20 | *

21 | */ 22 | public interface AsJson { 23 | @SneakyThrows 24 | default String toJson() { 25 | ObjectMapper objectMapper = new ObjectMapper(); 26 | return objectMapper.writerWithDefaultPrettyPrinter().writeValueAsString(this); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /ui-tests/src/test/java/io/company/utils/logging/Loggable.java: -------------------------------------------------------------------------------- 1 | package io.company.utils.logging; 2 | 3 | import org.slf4j.Logger; 4 | import org.slf4j.LoggerFactory; 5 | 6 | /** 7 | * Allows whoever implements {@link Loggable} interface to use a default logger implementation. 8 | */ 9 | public interface Loggable { 10 | default Logger logger() { 11 | return LoggerFactory.getLogger(this.getClass().getSimpleName()); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /ui-tests/src/test/java/io/company/utils/mocks/Mock.java: -------------------------------------------------------------------------------- 1 | package io.company.utils.mocks; 2 | 3 | import java.lang.annotation.ElementType; 4 | import java.lang.annotation.Retention; 5 | import java.lang.annotation.RetentionPolicy; 6 | import java.lang.annotation.Target; 7 | 8 | /** 9 | * Annotation to inject mocks during runtime. 10 | * Mocks are required to be json files. 11 | *
12 | * Example: {@code @Mock({"path1", "path2", ...})} 13 | */ 14 | @Target(ElementType.METHOD) 15 | @Retention(RetentionPolicy.RUNTIME) 16 | public @interface Mock { 17 | /** 18 | * Class that represents the mocked data. 19 | */ 20 | Class[] clazz(); 21 | } 22 | -------------------------------------------------------------------------------- /ui-tests/src/test/java/io/company/utils/mocks/MockDefinition.java: -------------------------------------------------------------------------------- 1 | package io.company.utils.mocks; 2 | 3 | import io.netty.handler.codec.http.HttpHeaders; 4 | import io.netty.handler.codec.http.HttpMethod; 5 | import io.netty.handler.codec.http.HttpResponseStatus; 6 | 7 | /** 8 | * Concrete definition of a mock. 9 | *
10 | */ 11 | public interface MockDefinition { 12 | String urlPattern(); 13 | 14 | default HttpMethod methodName() { 15 | return HttpMethod.GET; 16 | } 17 | 18 | default HttpResponseStatus responseStatus() { 19 | return HttpResponseStatus.OK; 20 | } 21 | 22 | default String contentBody() { 23 | return null; 24 | } 25 | 26 | default HttpHeaders headers() { 27 | return null; 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /ui-tests/src/test/java/io/company/utils/mocks/model/MockExampleModel.java: -------------------------------------------------------------------------------- 1 | package io.company.utils.mocks.model; 2 | 3 | import io.company.utils.mocks.MockDefinition; 4 | import io.netty.handler.codec.http.DefaultHttpHeaders; 5 | import io.netty.handler.codec.http.HttpHeaders; 6 | import io.netty.handler.codec.http.HttpMethod; 7 | import io.netty.handler.codec.http.HttpResponseStatus; 8 | 9 | public class MockExampleModel implements MockDefinition { 10 | 11 | @Override 12 | public String urlPattern() { 13 | return "https://www.google.pt/mock"; 14 | } 15 | 16 | @Override 17 | public HttpMethod methodName() { 18 | return HttpMethod.GET; 19 | } 20 | 21 | @Override 22 | public HttpResponseStatus responseStatus() { 23 | return HttpResponseStatus.OK; 24 | } 25 | 26 | @Override 27 | public String contentBody() { 28 | return "{\"MESSAGE\":\"MOCKED_MESSAGE\"}"; 29 | } 30 | 31 | @Override 32 | public HttpHeaders headers() { 33 | DefaultHttpHeaders defaultHttpHeaders = new DefaultHttpHeaders(); 34 | defaultHttpHeaders.set("random", "example"); 35 | return defaultHttpHeaders; 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /ui-tests/src/test/java/io/company/utils/reports/ExtentManager.java: -------------------------------------------------------------------------------- 1 | package io.company.utils.reports; 2 | 3 | 4 | import com.aventstack.extentreports.ExtentReports; 5 | import com.aventstack.extentreports.reporter.ExtentHtmlReporter; 6 | 7 | public class ExtentManager { 8 | 9 | /** 10 | * Extent report instance. 11 | */ 12 | private static ExtentReports extentReports; 13 | 14 | /** 15 | * Private constructor to avoid instantiation. 16 | */ 17 | private ExtentManager() { 18 | } 19 | 20 | /** 21 | * Call this method to get the extent report singleton instance. 22 | * 23 | * @return extent reports instance 24 | */ 25 | public static synchronized ExtentReports getInstance() { 26 | if (extentReports == null) { 27 | extentReports = new ExtentReports(); 28 | ExtentHtmlReporter htmlReporter = new ExtentHtmlReporter(System.getProperty("user.dir") + "/reports/ExtentReport.html"); 29 | htmlReporter.loadXMLConfig("reports/extent-config.xml"); 30 | extentReports.attachReporter(htmlReporter); 31 | 32 | // General information related to application 33 | extentReports.setSystemInfo("Application Name", "test automation bootstrap"); 34 | extentReports.setSystemInfo("User Name", "http://github.com/sergiomartins8"); 35 | extentReports.setSystemInfo("Environment", "Production"); 36 | } 37 | return extentReports; 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /ui-tests/src/test/java/io/company/utils/reports/ExtentTestReport.java: -------------------------------------------------------------------------------- 1 | package io.company.utils.reports; 2 | 3 | import com.aventstack.extentreports.ExtentTest; 4 | import io.company.utils.listeners.ExtentReportListener; 5 | 6 | import java.util.HashMap; 7 | import java.util.Map; 8 | 9 | /** 10 | * Extent reporter to be used by the {@link ExtentReportListener}. 11 | */ 12 | public class ExtentTestReport { 13 | 14 | /** 15 | * Holds the extent tests and corresponding threads in which they were executed. 16 | */ 17 | private static final Map EXTENT_TEST_MAP = new HashMap<>(); 18 | 19 | /** 20 | * Private constructor to avoid instantiation. 21 | */ 22 | private ExtentTestReport() { 23 | } 24 | 25 | public static synchronized ExtentTest getTest() { 26 | return EXTENT_TEST_MAP.get(Thread.currentThread().getId()); 27 | } 28 | 29 | public static synchronized void removeTest() { 30 | ExtentManager.getInstance().removeTest(EXTENT_TEST_MAP.get(Thread.currentThread().getId())); 31 | } 32 | 33 | public static synchronized void createTest(String testName, String desc) { 34 | ExtentTest test = ExtentManager.getInstance().createTest(testName, desc); 35 | EXTENT_TEST_MAP.put(Thread.currentThread().getId(), test); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /ui-tests/src/test/resources/checkstyle/checkstyle.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | -------------------------------------------------------------------------------- /ui-tests/src/test/resources/logback.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | %green(%d{yyyy-MM-dd HH:mm:ss.SSS}) %magenta([%thread]) %highlight(%-5level) %yellow(%logger{36}) - %msg%n 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /ui-tests/src/test/resources/reports/extent-config.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | dark 5 | UTF-8 6 | http 7 | Automation Report 8 | Automation Report 9 | MMM dd, yyyy HH:mm:ss 10 | 11 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /ui-tests/src/test/resources/suites/suiteA.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /ui-tests/src/test/resources/suites/suiteB.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | --------------------------------------------------------------------------------