├── .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 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
A simple and effective boilerplate repo to quickstart test automation frameworks
15 |
16 |
17 |
18 |
19 |
20 |
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 | 
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 | 
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 | 
13 |
14 | 
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 | 
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 | 
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 extends MockDefinition>[] 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 extends MockDefinition> 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 extends MockDefinition>[] 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 |
--------------------------------------------------------------------------------