├── src
├── main
│ ├── resources
│ │ ├── automation.properties
│ │ ├── reportportal.properties
│ │ └── log4j2.xml
│ └── java
│ │ └── automation
│ │ ├── enums
│ │ └── Title.java
│ │ ├── forms
│ │ ├── women
│ │ │ ├── CategoriesBlock.java
│ │ │ ├── InformationBlock.java
│ │ │ └── CatalogBlock.java
│ │ ├── CreateAccountForm.java
│ │ ├── HeaderForm.java
│ │ ├── YourAddressForm.java
│ │ └── PersonalInformationForm.java
│ │ ├── pages
│ │ ├── MyAccountPage.java
│ │ ├── DressesCategoryPage.java
│ │ ├── TShirtsCategoryPage.java
│ │ ├── AuthenticationPage.java
│ │ ├── CreateAccountPage.java
│ │ ├── WomenCategoryPage.java
│ │ └── IndexPage.java
│ │ ├── entities
│ │ ├── browser
│ │ │ ├── Browser.java
│ │ │ ├── FirefoxBrowser.java
│ │ │ └── ChromeBrowser.java
│ │ ├── Address.java
│ │ └── User.java
│ │ ├── utils
│ │ ├── Verify.java
│ │ ├── BaseTest.java
│ │ ├── LoadingPageFactory.java
│ │ ├── PropertiesReader.java
│ │ ├── BrowserConfiguration.java
│ │ └── MyListener.java
│ │ └── builders
│ │ ├── UserBuilder.java
│ │ └── AddressBuilder.java
└── test
│ ├── java
│ └── testcases
│ │ ├── RegistrationTest.java
│ │ ├── IndexPageTest.java
│ │ └── FailingCases.java
│ └── resources
│ ├── failing.xml
│ └── testng.xml
├── update_browsers.sh
├── config
└── browsers.json
├── LICENSE
├── .github
└── workflows
│ └── main.yml
├── .gitignore
├── README.md
├── pom.xml
└── docker-compose.yml
/src/main/resources/automation.properties:
--------------------------------------------------------------------------------
1 | chrome.version=90.0
2 | firefox.version=87.0
--------------------------------------------------------------------------------
/update_browsers.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | cat config/browsers.json | jq -r '..|.image?|strings' | xargs -I{} docker pull {}
3 |
--------------------------------------------------------------------------------
/src/main/java/automation/enums/Title.java:
--------------------------------------------------------------------------------
1 | package automation.enums;
2 |
3 | public enum Title {
4 | MR,
5 | MRS
6 | }
7 |
--------------------------------------------------------------------------------
/src/main/java/automation/forms/women/CategoriesBlock.java:
--------------------------------------------------------------------------------
1 | package automation.forms.women;
2 |
3 | import com.codeborne.selenide.ElementsContainer;
4 |
5 | public class CategoriesBlock extends ElementsContainer {
6 | }
7 |
--------------------------------------------------------------------------------
/src/main/java/automation/forms/women/InformationBlock.java:
--------------------------------------------------------------------------------
1 | package automation.forms.women;
2 |
3 | import com.codeborne.selenide.ElementsContainer;
4 |
5 | public class InformationBlock extends ElementsContainer {
6 | }
7 |
--------------------------------------------------------------------------------
/src/main/resources/reportportal.properties:
--------------------------------------------------------------------------------
1 | rp.endpoint = http://localhost:8080
2 | rp.uuid = 39a4de3d-5f80-426c-8e0c-3795eee0dccb
3 | rp.launch = Automation
4 | rp.project = Automation
5 | rp.enable = false
6 | rp.mode = DEFAULT
--------------------------------------------------------------------------------
/src/main/java/automation/pages/MyAccountPage.java:
--------------------------------------------------------------------------------
1 | package automation.pages;
2 |
3 | import automation.utils.Verify;
4 | import lombok.extern.slf4j.Slf4j;
5 |
6 | @Slf4j
7 | @Verify(title = "My account - My Store")
8 | public class MyAccountPage {
9 | }
10 |
--------------------------------------------------------------------------------
/src/main/java/automation/pages/DressesCategoryPage.java:
--------------------------------------------------------------------------------
1 | package automation.pages;
2 |
3 | import automation.utils.Verify;
4 | import lombok.extern.slf4j.Slf4j;
5 |
6 | @Slf4j
7 | @Verify(title = "Dresses - My Store")
8 | public class DressesCategoryPage {
9 | }
10 |
--------------------------------------------------------------------------------
/src/main/java/automation/pages/TShirtsCategoryPage.java:
--------------------------------------------------------------------------------
1 | package automation.pages;
2 |
3 | import automation.utils.Verify;
4 | import lombok.extern.slf4j.Slf4j;
5 |
6 | @Slf4j
7 | @Verify(title = "T-shirts - My Store")
8 | public class TShirtsCategoryPage {
9 | }
10 |
--------------------------------------------------------------------------------
/src/main/java/automation/forms/women/CatalogBlock.java:
--------------------------------------------------------------------------------
1 | package automation.forms.women;
2 |
3 | import com.codeborne.selenide.ElementsContainer;
4 | import com.codeborne.selenide.SelenideElement;
5 | import org.openqa.selenium.support.FindBy;
6 |
7 | public class CatalogBlock extends ElementsContainer {
8 | @FindBy(xpath = ".//*[@class='title_block']")
9 | private SelenideElement catalogBlock;
10 | }
11 |
--------------------------------------------------------------------------------
/src/main/java/automation/entities/browser/Browser.java:
--------------------------------------------------------------------------------
1 | package automation.entities.browser;
2 |
3 | import org.apache.commons.lang3.StringUtils;
4 | import org.openqa.selenium.remote.DesiredCapabilities;
5 |
6 | public interface Browser {
7 | String getType();
8 |
9 | String getVersion();
10 |
11 | DesiredCapabilities getCapabilities();
12 |
13 | default String getResolution() {
14 | return StringUtils.defaultIfEmpty(System.getProperty("browser.resolution"), "1920x1080");
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/src/main/java/automation/utils/Verify.java:
--------------------------------------------------------------------------------
1 | package automation.utils;
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 | @Retention(RetentionPolicy.RUNTIME)
9 | @Target(ElementType.TYPE)
10 | public @interface Verify {
11 | String INVALID_TITLE = "\0";
12 | String title() default INVALID_TITLE;
13 | String INVALID_XPATH = "\0";
14 | String xpath() default INVALID_XPATH;
15 | }
16 |
--------------------------------------------------------------------------------
/config/browsers.json:
--------------------------------------------------------------------------------
1 | {
2 | "chrome": {
3 | "default": "90.0",
4 | "versions": {
5 | "90.0": {
6 | "image": "selenoid/vnc:chrome_90.0",
7 | "port": "4444",
8 | "path": "/",
9 | "tmpfs": {
10 | "/tmp": "size=128m"
11 | }
12 | }
13 | }
14 | },
15 | "firefox": {
16 | "default": "87.0",
17 | "versions": {
18 | "87.0": {
19 | "image": "selenoid/vnc:firefox_87.0",
20 | "port": "4444",
21 | "path": "/wd/hub"
22 | }
23 | }
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/src/test/java/testcases/RegistrationTest.java:
--------------------------------------------------------------------------------
1 | package testcases;
2 |
3 | import automation.builders.UserBuilder;
4 | import automation.entities.User;
5 | import automation.utils.BaseTest;
6 | import org.testng.annotations.Test;
7 |
8 | public class RegistrationTest extends BaseTest {
9 | @Test
10 | public void userIsAbleToNavigateToAuthenticationPage() {
11 | openStartPage().navigateToAuthenticationPage();
12 | }
13 |
14 | @Test(description = "User is able to create account")
15 | public void userIsAbleToCreateAccount() {
16 | User user = new UserBuilder().build();
17 | openStartPage()
18 | .navigateToAuthenticationPage()
19 | .navigateToCreateAccountPage(user.getEmail())
20 | .fillAccountInfo(user)
21 | .register();
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/src/main/java/automation/pages/AuthenticationPage.java:
--------------------------------------------------------------------------------
1 | package automation.pages;
2 |
3 | import automation.utils.LoadingPageFactory;
4 | import automation.utils.Verify;
5 | import com.codeborne.selenide.SelenideElement;
6 | import lombok.extern.slf4j.Slf4j;
7 | import org.openqa.selenium.support.FindBy;
8 |
9 | @Slf4j
10 | @Verify(title = "Login - My Store")
11 | public class AuthenticationPage {
12 | @FindBy(id = "email_create")
13 | private SelenideElement emailAddressInput;
14 |
15 | @FindBy(id = "SubmitCreate")
16 | private SelenideElement createAccountButton;
17 |
18 | public CreateAccountPage navigateToCreateAccountPage(String email) {
19 | log.info("Logging in with email {}", email);
20 | emailAddressInput.setValue(email);
21 | createAccountButton.click();
22 | return LoadingPageFactory.get(CreateAccountPage.class);
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/src/main/java/automation/forms/CreateAccountForm.java:
--------------------------------------------------------------------------------
1 | package automation.forms;
2 |
3 | import automation.entities.User;
4 | import com.codeborne.selenide.ElementsContainer;
5 | import com.codeborne.selenide.SelenideElement;
6 | import org.openqa.selenium.support.FindBy;
7 |
8 | public class CreateAccountForm extends ElementsContainer {
9 | @FindBy(xpath = ".//h3[text()='Your personal information']/ancestor::div[1]")
10 | private PersonalInformationForm personalInformationForm;
11 |
12 | @FindBy(xpath = ".//h3[text()='Your address']/ancestor::div[1]")
13 | private YourAddressForm yourAddressForm;
14 |
15 | @FindBy(id = "submitAccount")
16 | private SelenideElement registerButton;
17 |
18 | public void fillPersonalInformation(User user) {
19 | personalInformationForm.fill(user);
20 | }
21 |
22 | public void fillYourAddress(User user) {
23 | yourAddressForm.fill(user);
24 | }
25 |
26 | public void register() {
27 | registerButton.click();
28 | }
29 | }
--------------------------------------------------------------------------------
/src/main/java/automation/utils/BaseTest.java:
--------------------------------------------------------------------------------
1 | package automation.utils;
2 |
3 | import automation.pages.IndexPage;
4 | import com.codeborne.selenide.Selenide;
5 | import lombok.extern.slf4j.Slf4j;
6 | import org.testng.annotations.AfterMethod;
7 | import org.testng.annotations.BeforeMethod;
8 | import org.testng.annotations.Optional;
9 | import org.testng.annotations.Parameters;
10 |
11 | @Slf4j
12 | public class BaseTest extends BrowserConfiguration {
13 | @BeforeMethod
14 | @Parameters({"browser"})
15 | public void setup(@Optional("chrome") String browserName) {
16 | setupBrowser(browserName);
17 | }
18 |
19 | @AfterMethod
20 | public void teardown() {
21 | Selenide.closeWebDriver();
22 | }
23 |
24 | public IndexPage openStartPage() {
25 | Selenide.open();
26 | String url = System.getProperty("url.address");
27 | log.info("Navigating to {}", url);
28 | Selenide.open(url);
29 | return LoadingPageFactory.get(IndexPage.class);
30 | }
31 |
32 | }
--------------------------------------------------------------------------------
/src/main/java/automation/pages/CreateAccountPage.java:
--------------------------------------------------------------------------------
1 | package automation.pages;
2 |
3 | import automation.entities.User;
4 | import automation.forms.CreateAccountForm;
5 | import automation.utils.LoadingPageFactory;
6 | import automation.utils.Verify;
7 | import lombok.extern.slf4j.Slf4j;
8 | import org.openqa.selenium.support.FindBy;
9 |
10 | @Slf4j
11 | @Verify(title = "Login - My Store")
12 | public class CreateAccountPage {
13 |
14 | @FindBy(id = "account-creation_form")
15 | private CreateAccountForm createAccountForm;
16 |
17 | public CreateAccountPage fillAccountInfo(User user) {
18 | log.info("Filling user information {}", user.toString());
19 | createAccountForm.fillPersonalInformation(user);
20 | createAccountForm.fillYourAddress(user);
21 | return this;
22 | }
23 |
24 | public MyAccountPage register() {
25 | log.info("Clicking REGISTER button");
26 | createAccountForm.register();
27 | return LoadingPageFactory.get(MyAccountPage.class);
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/src/test/resources/failing.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
--------------------------------------------------------------------------------
/src/main/java/automation/entities/browser/FirefoxBrowser.java:
--------------------------------------------------------------------------------
1 | package automation.entities.browser;
2 |
3 | import automation.utils.PropertiesReader;
4 | import com.codeborne.selenide.Browsers;
5 | import org.apache.commons.lang3.StringUtils;
6 | import org.openqa.selenium.remote.DesiredCapabilities;
7 |
8 | import java.util.Map;
9 |
10 | public class FirefoxBrowser implements Browser {
11 | @Override
12 | public String getType() {
13 | return Browsers.FIREFOX;
14 | }
15 |
16 | @Override
17 | public String getVersion() {
18 | Map properties = PropertiesReader.getProperties();
19 | String firefoxVersion = properties.get("firefox.version");
20 |
21 | return StringUtils.defaultString(firefoxVersion, "87.0");
22 | }
23 |
24 | @Override
25 | public DesiredCapabilities getCapabilities() {
26 | DesiredCapabilities capabilities = new DesiredCapabilities();
27 | capabilities.setCapability("enableVNC", true);
28 | capabilities.setCapability("enableVideo", false);
29 | return capabilities;
30 | }
31 | }
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2020 d3m0
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/src/main/java/automation/pages/WomenCategoryPage.java:
--------------------------------------------------------------------------------
1 | package automation.pages;
2 |
3 | import automation.forms.women.CatalogBlock;
4 | import automation.forms.women.CategoriesBlock;
5 | import automation.forms.women.InformationBlock;
6 | import automation.utils.Verify;
7 | import lombok.extern.slf4j.Slf4j;
8 | import org.openqa.selenium.support.FindBy;
9 |
10 | import static com.codeborne.selenide.Condition.visible;
11 |
12 | @Slf4j
13 | @Verify(title = "Women - My Store")
14 | public class WomenCategoryPage {
15 | @FindBy(id = "categories_block_left")
16 | private CategoriesBlock categoriesBlock;
17 |
18 | @FindBy(id = "layered_block_left")
19 | public CatalogBlock catalogBlock;
20 |
21 | @FindBy(id = "informations_block_left_1")
22 | public InformationBlock informationBlock;
23 |
24 | public WomenCategoryPage verifyBlocksPresent() {
25 | log.info("Verifying blocks are present on a page");
26 | categoriesBlock.getSelf().shouldBe(visible);
27 | catalogBlock.getSelf().shouldBe(visible);
28 | informationBlock.getSelf().shouldBe(visible);
29 | return this;
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/src/main/java/automation/entities/browser/ChromeBrowser.java:
--------------------------------------------------------------------------------
1 | package automation.entities.browser;
2 |
3 | import automation.utils.PropertiesReader;
4 | import com.codeborne.selenide.Browsers;
5 | import lombok.extern.slf4j.Slf4j;
6 | import org.apache.commons.lang3.StringUtils;
7 | import org.openqa.selenium.remote.DesiredCapabilities;
8 |
9 | import java.util.Map;
10 |
11 | @Slf4j
12 | public class ChromeBrowser implements Browser {
13 | @Override
14 | public String getType() {
15 | return Browsers.CHROME;
16 | }
17 |
18 | @Override
19 | public String getVersion() {
20 | Map properties = PropertiesReader.getProperties();
21 | String chromeVersion = properties.get("chrome.version");
22 |
23 | return StringUtils.defaultIfEmpty(chromeVersion, "88.0");
24 | }
25 |
26 | @Override
27 | public DesiredCapabilities getCapabilities() {
28 | DesiredCapabilities capabilities = new DesiredCapabilities();
29 | capabilities.setCapability("enableVNC", true);
30 | capabilities.setCapability("enableVideo", false);
31 | return capabilities;
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/src/test/resources/testng.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
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 |
--------------------------------------------------------------------------------
/.github/workflows/main.yml:
--------------------------------------------------------------------------------
1 | # This is a basic workflow to help you get started with Actions
2 |
3 | name: CI
4 |
5 | # Controls when the action will run. Triggers the workflow on push or pull request
6 | # events but only for the master branch
7 | on:
8 | push:
9 | branches: [ master ]
10 | pull_request:
11 | branches: [ master ]
12 |
13 | # A workflow run is made up of one or more jobs that can run sequentially or in parallel
14 | jobs:
15 | # This workflow contains a single job called "build"
16 | build:
17 | # The type of runner that the job will run on
18 | runs-on: ubuntu-latest
19 |
20 | # Steps represent a sequence of tasks that will be executed as part of the job
21 | steps:
22 | # Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it
23 | - uses: actions/checkout@v2
24 |
25 | - name: Set up JDK 11
26 | uses: actions/setup-java@v1
27 | with:
28 | java-version: '11.0.3'
29 | - name: Start Selenoid
30 | run: curl -s https://aerokube.com/cm/bash | bash && ./cm selenoid start --browsers 'firefox:87.0;chrome:90.0'
31 | - name: Execute test-cases with Maven
32 | run: mvn test -Drp.enable=false -Dselenoid.enabled=true
33 |
--------------------------------------------------------------------------------
/src/test/java/testcases/IndexPageTest.java:
--------------------------------------------------------------------------------
1 | package testcases;
2 |
3 | import automation.pages.IndexPage;
4 | import automation.utils.BaseTest;
5 | import org.testng.annotations.Test;
6 |
7 | public class IndexPageTest extends BaseTest {
8 | @Test(description = "Verify that starting page opens")
9 | public void verifyIndexPageOpened() {
10 | openStartPage();
11 | }
12 |
13 | @Test
14 | public void verifyTopMenu() {
15 | openStartPage().verifyTopMenu();
16 | }
17 |
18 | @Test
19 | public void userIsAbleToNavigateToWomenCategory() {
20 | openStartPage().navigateToWomenCategory();
21 | }
22 |
23 | @Test(dependsOnMethods = "userIsAbleToNavigateToWomenCategory")
24 | public void verifyWomenCategoryPageContainsAllBlocks() {
25 | openStartPage().navigateToWomenCategory().verifyBlocksPresent();
26 | }
27 |
28 | @Test
29 | public void userIsAbleToNavigateToDressesCategory() {
30 | IndexPage indexPage = openStartPage();
31 | indexPage.navigateToDressesCategory();
32 | }
33 |
34 | @Test
35 | public void userIsAbleToNavigateToTShirtsCategory() {
36 | IndexPage indexPage = openStartPage();
37 | indexPage.navigateToTShirtsCategory();
38 | }
39 | }
--------------------------------------------------------------------------------
/src/main/java/automation/forms/HeaderForm.java:
--------------------------------------------------------------------------------
1 | package automation.forms;
2 |
3 | import com.codeborne.selenide.ElementsContainer;
4 | import com.codeborne.selenide.SelenideElement;
5 | import org.openqa.selenium.support.FindBy;
6 |
7 | import static com.codeborne.selenide.Condition.visible;
8 |
9 | public class HeaderForm extends ElementsContainer {
10 | @FindBy(xpath = ".//ul[not(contains(@style, 'display: none'))]/li/a[@title='Women']")
11 | private SelenideElement womenButton;
12 |
13 | @FindBy(xpath = ".//ul[not(contains(@style, 'display: none'))]/li/a[@title='Dresses']")
14 | private SelenideElement dressesButton;
15 |
16 | @FindBy(xpath = ".//ul[not(contains(@style, 'display: none'))]/li/a[@title='T-shirts']")
17 | private SelenideElement tShirtsButton;
18 |
19 | @FindBy(xpath = ".//nav//a[@class='login']")
20 | private SelenideElement signInButton;
21 |
22 | public void verifyLinkArePresent() {
23 | womenButton.shouldBe(visible);
24 | dressesButton.shouldBe(visible);
25 | tShirtsButton.shouldBe(visible);
26 | }
27 |
28 | public void clickWomenCategory() {
29 | womenButton.click();
30 | }
31 |
32 | public void clickDressesCategory() {
33 | dressesButton.click();
34 | }
35 |
36 | public void clickTShirtsCategory() {
37 | tShirtsButton.click();
38 | }
39 |
40 | public void clickSignInButton() {
41 | signInButton.click();
42 | }
43 | }
--------------------------------------------------------------------------------
/src/main/java/automation/forms/YourAddressForm.java:
--------------------------------------------------------------------------------
1 | package automation.forms;
2 |
3 | import automation.entities.Address;
4 | import automation.entities.User;
5 | import com.codeborne.selenide.ElementsContainer;
6 | import com.codeborne.selenide.SelenideElement;
7 | import org.openqa.selenium.support.FindBy;
8 |
9 | public class YourAddressForm extends ElementsContainer {
10 | @FindBy(id = "address1")
11 | private SelenideElement line1Input;
12 |
13 | @FindBy(id = "address2")
14 | private SelenideElement line2Input;
15 |
16 | @FindBy(id = "city")
17 | private SelenideElement cityInput;
18 |
19 | @FindBy(id = "id_state")
20 | private SelenideElement stateDropdown;
21 |
22 | @FindBy(id = "postcode")
23 | private SelenideElement zipInput;
24 |
25 | @FindBy(id = "id_country")
26 | private SelenideElement countryDropdown;
27 |
28 | @FindBy(id = "phone_mobile")
29 | private SelenideElement mobilePhoneInput;
30 |
31 | public void fill(User user) {
32 | Address address = user.getAddress();
33 | line1Input.setValue(address.getLine1());
34 | line2Input.setValue(address.getLine2());
35 | cityInput.setValue(address.getCity());
36 | stateDropdown.selectOption(address.getState());
37 | zipInput.setValue(address.getZip());
38 | countryDropdown.selectOption(address.getCountry());
39 | mobilePhoneInput.setValue(user.getMobilePhone());
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/src/main/java/automation/pages/IndexPage.java:
--------------------------------------------------------------------------------
1 | package automation.pages;
2 |
3 | import automation.forms.HeaderForm;
4 | import automation.utils.LoadingPageFactory;
5 | import automation.utils.Verify;
6 | import lombok.extern.slf4j.Slf4j;
7 | import org.openqa.selenium.support.FindBy;
8 |
9 | @Slf4j
10 | @Verify(title = "My Store")
11 | public class IndexPage {
12 |
13 | @FindBy(id = "header")
14 | private HeaderForm headerForm;
15 |
16 | public void verifyTopMenu() {
17 | log.info("Verifying Top Menu block");
18 | headerForm.verifyLinkArePresent();
19 | }
20 |
21 | public WomenCategoryPage navigateToWomenCategory() {
22 | log.info("Navigating to WOMEN category");
23 | headerForm.clickWomenCategory();
24 | return LoadingPageFactory.get(WomenCategoryPage.class);
25 | }
26 |
27 | public DressesCategoryPage navigateToDressesCategory() {
28 | log.info("Navigating to DRESSES category");
29 | headerForm.clickDressesCategory();
30 | return LoadingPageFactory.get(DressesCategoryPage.class);
31 | }
32 |
33 | public TShirtsCategoryPage navigateToTShirtsCategory() {
34 | log.info("Navigating to T-SHIRTS category");
35 | headerForm.clickTShirtsCategory();
36 | return LoadingPageFactory.get(TShirtsCategoryPage.class);
37 | }
38 |
39 | public AuthenticationPage navigateToAuthenticationPage() {
40 | log.info("Navigating to Authentication page");
41 | headerForm.clickSignInButton();
42 | return LoadingPageFactory.get(AuthenticationPage.class);
43 | }
44 | }
--------------------------------------------------------------------------------
/src/main/java/automation/entities/Address.java:
--------------------------------------------------------------------------------
1 | package automation.entities;
2 |
3 | import automation.builders.AddressBuilder;
4 |
5 | public class Address {
6 | private final String line1;
7 | private final String line2;
8 | private final String city;
9 | private final String state;
10 | private final String zip;
11 | private final String country;
12 |
13 | public Address(AddressBuilder addressBuilder) {
14 | this.line1 = addressBuilder.getLine1();
15 | this.line2 = addressBuilder.getLine2();
16 | this.city = addressBuilder.getCity();
17 | this.state = addressBuilder.getState();
18 | this.zip = addressBuilder.getZip();
19 | this.country = addressBuilder.getCountry();
20 | }
21 |
22 | public String getLine1() {
23 | return line1;
24 | }
25 |
26 | public String getLine2() {
27 | return line2;
28 | }
29 |
30 | public String getCity() {
31 | return city;
32 | }
33 |
34 | public String getState() {
35 | return state;
36 | }
37 |
38 | public String getZip() {
39 | return zip;
40 | }
41 |
42 | public String getCountry() {
43 | return country;
44 | }
45 |
46 | @Override
47 | public String toString() {
48 | return "Address{" +
49 | "line1='" + line1 + '\'' +
50 | ", line2='" + line2 + '\'' +
51 | ", city='" + city + '\'' +
52 | ", state='" + state + '\'' +
53 | ", zip='" + zip + '\'' +
54 | ", country='" + country + '\'' +
55 | '}';
56 | }
57 | }
--------------------------------------------------------------------------------
/src/main/java/automation/utils/LoadingPageFactory.java:
--------------------------------------------------------------------------------
1 | package automation.utils;
2 |
3 | import com.codeborne.selenide.Selenide;
4 | import com.codeborne.selenide.WebDriverRunner;
5 | import lombok.extern.slf4j.Slf4j;
6 | import org.openqa.selenium.By;
7 | import org.openqa.selenium.ElementNotVisibleException;
8 | import org.openqa.selenium.WebDriver;
9 | import org.testng.Assert;
10 |
11 | @Slf4j
12 | public class LoadingPageFactory {
13 | public static T get(Class pageObjectClass) {
14 | WebDriver driver = WebDriverRunner.getWebDriver();
15 |
16 | String simpleName = pageObjectClass.getSimpleName();
17 | log.info("Initializing page");
18 | Verify verify = pageObjectClass.getAnnotation(Verify.class);
19 |
20 | String expectedPageTitle;
21 | try {
22 | expectedPageTitle = verify.title();
23 | } catch (NullPointerException exception) {
24 | throw new ElementNotVisibleException(String.format("Please use @Verify annotation for %s page", simpleName));
25 | }
26 |
27 | if (!expectedPageTitle.equals(Verify.INVALID_TITLE)) {
28 | String actualPageTitle = driver.getTitle();
29 | Assert.assertEquals(actualPageTitle, expectedPageTitle, "Page title is correct.");
30 | }
31 |
32 | String xpath = verify.xpath();
33 | if (!xpath.equals(Verify.INVALID_XPATH)) {
34 | if (driver.findElements(By.xpath(xpath)).isEmpty()) {
35 | throw new IllegalStateException(String.format("expected XPath %s", xpath));
36 | }
37 | }
38 |
39 | return Selenide.page(pageObjectClass);
40 | }
41 | }
--------------------------------------------------------------------------------
/src/main/java/automation/utils/PropertiesReader.java:
--------------------------------------------------------------------------------
1 | package automation.utils;
2 |
3 | import lombok.extern.slf4j.Slf4j;
4 |
5 | import java.io.BufferedReader;
6 | import java.io.IOException;
7 | import java.io.InputStream;
8 | import java.io.InputStreamReader;
9 | import java.nio.charset.StandardCharsets;
10 | import java.util.HashMap;
11 | import java.util.Map;
12 |
13 | /**
14 | * @author Andrii Mogyla
15 | * @since 19.04.2021
16 | */
17 | @Slf4j
18 | public class PropertiesReader {
19 | public static Map getProperties() {
20 | InputStream fileFromResourceAsStream = getFileFromResourceAsStream();
21 |
22 | Map result = new HashMap<>();
23 | try (InputStreamReader streamReader = new InputStreamReader(fileFromResourceAsStream, StandardCharsets.UTF_8);
24 | BufferedReader reader = new BufferedReader(streamReader)) {
25 |
26 | String line;
27 | while ((line = reader.readLine()) != null) {
28 | String[] split = line.split("=");
29 | result.put(split[0].trim(), split[1].trim());
30 | }
31 |
32 | } catch (IOException e) {
33 | log.error(e.getMessage(), e);
34 | }
35 |
36 | return result;
37 | }
38 |
39 | private static InputStream getFileFromResourceAsStream() {
40 | String fileName = "automation.properties";
41 | // The class loader that loaded the class
42 | ClassLoader classLoader = PropertiesReader.class.getClassLoader();
43 | InputStream inputStream = classLoader.getResourceAsStream(fileName);
44 |
45 | // the stream holding the file content
46 | if (inputStream == null) {
47 | throw new IllegalArgumentException("file not found! " + fileName);
48 | } else {
49 | return inputStream;
50 | }
51 | }
52 | }
--------------------------------------------------------------------------------
/src/main/java/automation/utils/BrowserConfiguration.java:
--------------------------------------------------------------------------------
1 | package automation.utils;
2 |
3 | import automation.entities.browser.Browser;
4 | import automation.entities.browser.ChromeBrowser;
5 | import automation.entities.browser.FirefoxBrowser;
6 | import com.codeborne.selenide.Configuration;
7 | import lombok.extern.slf4j.Slf4j;
8 | import org.apache.commons.lang3.NotImplementedException;
9 |
10 | /**
11 | * @author Andrii Mogyla
12 | * @since 19.04.2021
13 | */
14 | @Slf4j
15 | public class BrowserConfiguration {
16 | public void setupBrowser(String browserName) {
17 | log.info("Setting up {} driver", browserName);
18 | Browser browser;
19 | switch (browserName) {
20 | case "chrome":
21 | browser = new ChromeBrowser();
22 | break;
23 | case "firefox":
24 | browser = new FirefoxBrowser();
25 | break;
26 | default:
27 | log.error("BROWSER NOT FOUND");
28 | throw new NotImplementedException(String.format("Browser '%s' not found. Please implement", browserName));
29 | }
30 | setupDriver(browser);
31 | }
32 |
33 | private void setupDriver(Browser browser) {
34 | if (Boolean.parseBoolean(System.getProperty("selenoid.enabled"))) {
35 | log.info("Setting up remote WebDriver for Selenoid");
36 | String selenoidHubAddress = System.getProperty("selenoid.hub.address");
37 | Configuration.remote = selenoidHubAddress + "/wd/hub";
38 | Configuration.browser = browser.getType();
39 | Configuration.browserVersion = browser.getVersion();
40 | Configuration.browserSize = browser.getResolution();
41 | Configuration.browserCapabilities = browser.getCapabilities();
42 | } else {
43 | log.info("Setting up local WebDriver");
44 | Configuration.browser = browser.getType();
45 | Configuration.browserSize = browser.getResolution();
46 | }
47 | }
48 | }
--------------------------------------------------------------------------------
/src/main/resources/log4j2.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
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 |
--------------------------------------------------------------------------------
/src/main/java/automation/forms/PersonalInformationForm.java:
--------------------------------------------------------------------------------
1 | package automation.forms;
2 |
3 | import automation.entities.User;
4 | import automation.enums.Title;
5 | import com.codeborne.selenide.ElementsContainer;
6 | import com.codeborne.selenide.SelenideElement;
7 | import org.openqa.selenium.support.FindBy;
8 |
9 | import java.time.LocalDate;
10 |
11 | public class PersonalInformationForm extends ElementsContainer {
12 | @FindBy(xpath = ".//label[normalize-space()='Mr.']")
13 | private SelenideElement titleMrRadio;
14 |
15 | @FindBy(xpath = ".//label[normalize-space()='Mrs.']")
16 | private SelenideElement titleMrsRadio;
17 |
18 | @FindBy(id = "customer_firstname")
19 | private SelenideElement firstNameInput;
20 |
21 | @FindBy(id = "customer_lastname")
22 | private SelenideElement lastNameInput;
23 |
24 | @FindBy(id = "email")
25 | private SelenideElement emailInput;
26 |
27 | @FindBy(id = "passwd")
28 | private SelenideElement passwordInput;
29 |
30 | @FindBy(id = "days")
31 | private SelenideElement dobDaysDropdown;
32 |
33 | @FindBy(id = "months")
34 | private SelenideElement dobMonthsDropdown;
35 |
36 | @FindBy(id = "years")
37 | private SelenideElement dobYearsDropdown;
38 |
39 | public void fill(User user) {
40 | if (Title.MR == user.getTitle()) {
41 | titleMrRadio.click();
42 | } else {
43 | titleMrsRadio.click();
44 | }
45 |
46 | firstNameInput.setValue(user.getFirstName());
47 | lastNameInput.setValue(user.getLastName());
48 | emailInput.setValue(user.getEmail());
49 | passwordInput.setValue(user.getPassword());
50 | fillDateOfBirth(user.getDateOfBirth());
51 | }
52 |
53 | private void fillDateOfBirth(LocalDate dateOfBirth) {
54 | int day = dateOfBirth.getDayOfMonth();
55 | int month = dateOfBirth.getMonthValue();
56 | int year = dateOfBirth.getYear();
57 |
58 | dobDaysDropdown.selectOptionByValue(String.valueOf(day));
59 | dobMonthsDropdown.selectOptionByValue(String.valueOf(month));
60 | dobYearsDropdown.selectOptionByValue(String.valueOf(year));
61 | }
62 | }
--------------------------------------------------------------------------------
/src/main/java/automation/entities/User.java:
--------------------------------------------------------------------------------
1 | package automation.entities;
2 |
3 | import automation.builders.UserBuilder;
4 | import automation.enums.Title;
5 |
6 | import java.time.LocalDate;
7 |
8 | public class User {
9 | private final Title title;
10 | private final String firstName;
11 | private final String lastName;
12 | private final String email;
13 | private final String password;
14 | private final LocalDate dateOfBirth;
15 | private final Address address;
16 | private final String mobilePhone;
17 |
18 | public User(UserBuilder builder) {
19 | this.title = builder.getTitle();
20 | this.firstName = builder.getFirstName();
21 | this.lastName = builder.getLastName();
22 | this.email = builder.getEmail();
23 | this.password = builder.getPassword();
24 | this.dateOfBirth = builder.getDateOfBirth();
25 | this.address = builder.getAddress();
26 | this.mobilePhone = builder.getMobilePhone();
27 | }
28 |
29 | public Title getTitle() {
30 | return title;
31 | }
32 |
33 | public String getFirstName() {
34 | return firstName;
35 | }
36 |
37 | public String getLastName() {
38 | return lastName;
39 | }
40 |
41 | public String getEmail() {
42 | return email;
43 | }
44 |
45 | public String getPassword() {
46 | return password;
47 | }
48 |
49 | public LocalDate getDateOfBirth() {
50 | return dateOfBirth;
51 | }
52 |
53 | public Address getAddress() {
54 | return address;
55 | }
56 |
57 | public String getMobilePhone() {
58 | return mobilePhone;
59 | }
60 |
61 | @Override
62 | public String toString() {
63 | return "User{" +
64 | "title=" + title +
65 | ", firstName='" + firstName + '\'' +
66 | ", lastName='" + lastName + '\'' +
67 | ", email='" + email + '\'' +
68 | ", password='" + password + '\'' +
69 | ", dateOfBirth=" + dateOfBirth +
70 | ", address=" + address +
71 | ", mobilePhone='" + mobilePhone + '\'' +
72 | '}';
73 | }
74 | }
--------------------------------------------------------------------------------
/src/test/java/testcases/FailingCases.java:
--------------------------------------------------------------------------------
1 | package testcases;
2 |
3 | import automation.utils.BaseTest;
4 | import com.codeborne.selenide.Condition;
5 | import com.codeborne.selenide.Selenide;
6 | import com.codeborne.selenide.SelenideElement;
7 | import com.codeborne.selenide.ex.ElementNotFound;
8 | import lombok.extern.slf4j.Slf4j;
9 | import org.apache.commons.lang3.NotImplementedException;
10 | import org.openqa.selenium.By;
11 | import org.testng.annotations.*;
12 |
13 | @Slf4j
14 | public class FailingCases extends BaseTest {
15 | private static final String INCORRECT_XPATH = ".//ul[not(contains(@style, 'display: none'))]/li/a[@title='T-shirts']/span";
16 |
17 | @BeforeSuite
18 | public void setup(@Optional("chrome") String browserName) {
19 | }
20 |
21 | @BeforeTest
22 | public void setup1(@Optional("chrome") String browserName) {
23 | log.debug("Setting up browser for test elementIsNotPresentOnAPage");
24 | setupBrowser(browserName);
25 | }
26 |
27 | @Test(description = "Verify test fails because of element is not present on a page", expectedExceptions = ElementNotFound.class)
28 | public void elementIsNotPresentOnAPage() {
29 | openStartPage();
30 | SelenideElement tShirtsButton = Selenide.$(By.xpath(INCORRECT_XPATH));
31 | tShirtsButton.shouldBe(Condition.visible);
32 | }
33 |
34 | @BeforeTest
35 | public void setup2(@Optional("chrome") String browserName) {
36 | log.debug("Setting up browser for test clickNonexistentElement");
37 | setupBrowser(browserName);
38 | }
39 |
40 | @Test(description = "Verify test fails when trying to click nonexistent element", expectedExceptions = ElementNotFound.class)
41 | public void clickNonexistentElement() {
42 | openStartPage();
43 | SelenideElement tShirtsButton = Selenide.$(By.xpath(INCORRECT_XPATH));
44 | tShirtsButton.click();
45 | }
46 |
47 | @Test(description = "Verify incorrect browser behaviour", expectedExceptions = NotImplementedException.class)
48 | @Parameters({"IncorrectBrowser"})
49 | public void incorrectBrowser(@Optional("IncorrectBrowser") String browser) {
50 | setupBrowser(browser);
51 | openStartPage();
52 | }
53 | }
--------------------------------------------------------------------------------
/src/main/java/automation/utils/MyListener.java:
--------------------------------------------------------------------------------
1 | package automation.utils;
2 |
3 | import com.codeborne.selenide.WebDriverRunner;
4 | import com.epam.reportportal.testng.ReportPortalTestNGListener;
5 | import lombok.extern.slf4j.Slf4j;
6 | import org.apache.commons.io.FileUtils;
7 | import org.openqa.selenium.OutputType;
8 | import org.openqa.selenium.TakesScreenshot;
9 | import org.openqa.selenium.WebDriver;
10 | import org.testng.ITestResult;
11 | import ru.yandex.qatools.ashot.AShot;
12 | import ru.yandex.qatools.ashot.Screenshot;
13 | import ru.yandex.qatools.ashot.shooting.ShootingStrategies;
14 |
15 | import javax.imageio.ImageIO;
16 | import java.io.File;
17 | import java.io.IOException;
18 |
19 | @Slf4j
20 | public class MyListener extends ReportPortalTestNGListener {
21 |
22 | public MyListener() {
23 | super();
24 | }
25 |
26 | @Override
27 | public void onTestFailure(ITestResult testResult) {
28 | String className = testResult.getTestClass().getName().trim();
29 | String methodName = testResult.getMethod().getMethodName().trim();
30 |
31 | WebDriver webDriver = WebDriverRunner.getWebDriver();
32 | File destFile = new File("build/reports/tests/" + className + "/" + methodName + "/" + System.currentTimeMillis() + ".png");
33 |
34 | try {
35 | takeScreenshotWithSeleniumWebDriver((TakesScreenshot) webDriver, destFile);
36 | getScreenshotByAShot(webDriver, destFile);
37 | } catch (IOException exception) {
38 | log.error(exception.getMessage(), exception);
39 | }
40 |
41 | log.error("RP_MESSAGE#FILE#{}#{}", destFile, "Screenshot");
42 | super.onTestFailure(testResult);
43 | }
44 |
45 | private void takeScreenshotWithSeleniumWebDriver(TakesScreenshot webDriver, File destFile) throws IOException {
46 | File tmpFile = webDriver.getScreenshotAs(OutputType.FILE);
47 | FileUtils.copyFile(tmpFile, destFile);
48 | }
49 |
50 | private void getScreenshotByAShot(WebDriver webDriver, File destFile) throws IOException {
51 | Screenshot screenshot = new AShot()
52 | .shootingStrategy(ShootingStrategies.viewportPasting(10))
53 | .takeScreenshot(webDriver);
54 | ImageIO.write(screenshot.getImage(), "PNG", destFile);
55 | }
56 | }
--------------------------------------------------------------------------------
/src/main/java/automation/builders/UserBuilder.java:
--------------------------------------------------------------------------------
1 | package automation.builders;
2 |
3 | import automation.entities.Address;
4 | import automation.entities.User;
5 | import automation.enums.Title;
6 | import com.github.javafaker.Faker;
7 |
8 | import java.time.LocalDate;
9 | import java.time.ZoneId;
10 |
11 | public class UserBuilder {
12 | private Title title;
13 | private String firstName;
14 | private String lastName;
15 | private String email;
16 | private String password;
17 | private LocalDate dateOfBirth;
18 | private Address address;
19 | private String mobilePhone;
20 |
21 | public UserBuilder() {
22 | Faker faker = new Faker();
23 | this.title = (faker.name().prefix().length() > 3) ? Title.MRS : Title.MR;
24 | this.firstName = faker.name().firstName();
25 | this.lastName = faker.name().lastName();
26 | this.email = faker.internet().emailAddress();
27 | this.password = faker.internet().password();
28 | this.dateOfBirth = faker.date().birthday().toInstant().atZone(ZoneId.systemDefault()).toLocalDate();
29 | this.address = new AddressBuilder().build();
30 | this.mobilePhone = faker.phoneNumber().cellPhone();
31 | }
32 |
33 | public Title getTitle() {
34 | return title;
35 | }
36 |
37 | public UserBuilder setTitle(Title title) {
38 | this.title = title;
39 | return this;
40 | }
41 |
42 | public String getFirstName() {
43 | return firstName;
44 | }
45 |
46 | public UserBuilder setFirstName(String firstName) {
47 | this.firstName = firstName;
48 | return this;
49 | }
50 |
51 | public String getLastName() {
52 | return lastName;
53 | }
54 |
55 | public UserBuilder setLastName(String lastName) {
56 | this.lastName = lastName;
57 | return this;
58 | }
59 |
60 | public String getEmail() {
61 | return email;
62 | }
63 |
64 | public UserBuilder setEmail(String email) {
65 | this.email = email;
66 | return this;
67 | }
68 |
69 | public String getPassword() {
70 | return password;
71 | }
72 |
73 | public UserBuilder setPassword(String password) {
74 | this.password = password;
75 | return this;
76 | }
77 |
78 | public LocalDate getDateOfBirth() {
79 | return dateOfBirth;
80 | }
81 |
82 | public UserBuilder setDateOfBirth(LocalDate dateOfBirth) {
83 | this.dateOfBirth = dateOfBirth;
84 | return this;
85 | }
86 |
87 | public Address getAddress() {
88 | return address;
89 | }
90 |
91 | public UserBuilder setAddress(Address address) {
92 | this.address = address;
93 | return this;
94 | }
95 |
96 | public String getMobilePhone() {
97 | return mobilePhone;
98 | }
99 |
100 | public UserBuilder setMobilePhone(String mobilePhone) {
101 | this.mobilePhone = mobilePhone;
102 | return this;
103 | }
104 |
105 | public User build() {
106 | return new User(this);
107 | }
108 | }
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Created by https://www.gitignore.io/api/java,windows,intellij
2 | # Edit at https://www.gitignore.io/?templates=java,windows,intellij
3 |
4 | ### Intellij ###
5 | # Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and WebStorm
6 | # Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839
7 |
8 | # User-specific stuff
9 | .idea/**/workspace.xml
10 | .idea/**/tasks.xml
11 | .idea/**/usage.statistics.xml
12 | .idea/**/dictionaries
13 | .idea/**/shelf
14 | .idea/**/codeStiles/
15 | .idea/**/jarRepositories.xml
16 |
17 | # Generated files
18 | .idea/**/contentModel.xml
19 |
20 | # Sensitive or high-churn files
21 | .idea/**/dataSources/
22 | .idea/**/dataSources.ids
23 | .idea/**/dataSources.local.xml
24 | .idea/**/sqlDataSources.xml
25 | .idea/**/dynamic.xml
26 | .idea/**/uiDesigner.xml
27 | .idea/**/dbnavigator.xml
28 |
29 | # Gradle
30 | .idea/**/gradle.xml
31 | .idea/**/libraries
32 |
33 | # Gradle and Maven with auto-import
34 | # When using Gradle or Maven with auto-import, you should exclude module files,
35 | # since they will be recreated, and may cause churn. Uncomment if using
36 | # auto-import.
37 | .idea/modules.xml
38 | # .idea/*.iml
39 | # .idea/modules
40 | # *.iml
41 | # *.ipr
42 |
43 | # CMake
44 | cmake-build-*/
45 |
46 | # Mongo Explorer plugin
47 | .idea/**/mongoSettings.xml
48 |
49 | # File-based project format
50 | *.iws
51 |
52 | # IntelliJ
53 | out/
54 |
55 | # mpeltonen/sbt-idea plugin
56 | .idea_modules/
57 |
58 | # JIRA plugin
59 | atlassian-ide-plugin.xml
60 |
61 | # Cursive Clojure plugin
62 | .idea/replstate.xml
63 |
64 | # Crashlytics plugin (for Android Studio and IntelliJ)
65 | com_crashlytics_export_strings.xml
66 | crashlytics.properties
67 | crashlytics-build.properties
68 | fabric.properties
69 |
70 | # Editor-based Rest Client
71 | .idea/httpRequests
72 |
73 | # Android studio 3.1+ serialized cache file
74 | .idea/caches/build_file_checksums.ser
75 |
76 | ### Intellij Patch ###
77 | # Comment Reason: https://github.com/joeblau/gitignore.io/issues/186#issuecomment-215987721
78 |
79 | # *.iml
80 | # modules.xml
81 | .idea/misc.xml
82 | # *.ipr
83 |
84 | # Sonarlint plugin
85 | .idea/**/sonarlint/
86 |
87 | # SonarQube Plugin
88 | .idea/**/sonarIssues.xml
89 |
90 | # Markdown Navigator plugin
91 | .idea/**/markdown-navigator.xml
92 | .idea/**/markdown-navigator/
93 |
94 | ### Java ###
95 | # Compiled class file
96 | *.class
97 |
98 | # Log file
99 | *.log
100 |
101 | # BlueJ files
102 | *.ctxt
103 |
104 | # Mobile Tools for Java (J2ME)
105 | .mtj.tmp/
106 |
107 | # Package Files #
108 | *.jar
109 | *.war
110 | *.nar
111 | *.ear
112 | *.zip
113 | *.tar.gz
114 | *.rar
115 |
116 | # virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml
117 | hs_err_pid*
118 |
119 | ### Windows ###
120 | # Windows thumbnail cache files
121 | Thumbs.db
122 | Thumbs.db:encryptable
123 | ehthumbs.db
124 | ehthumbs_vista.db
125 |
126 | # Dump file
127 | *.stackdump
128 |
129 | # Folder config file
130 | [Dd]esktop.ini
131 |
132 | # Recycle Bin used on file shares
133 | $RECYCLE.BIN/
134 |
135 | # Windows Installer files
136 | *.cab
137 | *.msi
138 | *.msix
139 | *.msm
140 | *.msp
141 |
142 | # Windows shortcuts
143 | *.lnk
144 |
145 | # End of https://www.gitignore.io/api/java,windows,intellij
146 | build/reports/
147 | data/
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Automation Framework #
2 |
3 | **Environment**
4 | ***Platform***: Linux/MacOS/Windows
5 | ***Java***: 11
6 | ***Maven***: 3.8.1
7 | ***Docker***: 19.03.8
8 |
9 | **Frameworks/Libraries**
10 | ***Selenide***: 5.20.4 - WebDriver
11 | ***Selenoid***: 1.10.3 - Hub
12 | ***Selenoid-UI***: 1.10.3 - Selenoid UI
13 | ***TestNG***: 7.4.0 - Testing Framework
14 | ***ReportPortal***: 5.3.5 - Reporting
15 |
16 | Stack:
17 | - Selenide (test automation framework)
18 | - Selenoid (Hub)
19 | - TestNG (testing framework)
20 | - Maven (project management tool)
21 | - ReportPortal (reporting tool)
22 |
23 | ```
24 | ├─── .github
25 | │ └─── workflows
26 | │ └─── main.yml
27 | ├─── build
28 | │ └─── reports
29 | │ └─── tests
30 | ├─── config
31 | │ └─── browsers.json
32 | ├─── src
33 | │ ├─── main
34 | │ │ ├─── java
35 | │ │ │ ├─── automation.builders
36 | │ │ │ │ └─── **/*Builder.java
37 | │ │ │ ├─── automation.entities
38 | │ │ │ │ ├─── browser
39 | │ │ │ │ │ ├─── Browser.java
40 | │ │ │ │ │ ├─── ChromeBrowser.java
41 | │ │ │ │ │ └─── FirefoxBrowser.java
42 | │ │ │ │ └─── **/**.java
43 | │ │ │ ├─── automation.enums
44 | │ │ │ │ └─── **/**.java
45 | │ │ │ ├─── automation.forms
46 | │ │ │ │ └─── **/*Form.java
47 | │ │ │ ├─── automation.pages
48 | │ │ │ │ └─── **/*Page.java
49 | │ │ │ └─── com.d3m0.automation.utils
50 | │ │ │ ├─── BaseTest.java
51 | │ │ │ ├─── LoadingPageFactory.java
52 | │ │ │ ├─── MyListener.java
53 | │ │ │ └─── Verify.java
54 | │ │ └─── resources
55 | │ │ ├─── log4j2.xml
56 | │ └─── reportportal.properties
57 | │ └─── test
58 | │ ├─── java
59 | │ │ └─── testcases
60 | │ │ └─── **/*Test.java
61 | │ └─── resources
62 | │ └─── testng.xml
63 | ├─── .gitignore
64 | ├─── docker-compose.yml
65 | ├─── pom.xml
66 | ├─── README.md
67 | └─── update_browsers.sh
68 | ```
69 |
70 | **Installation**
71 | 1. Clone project.
72 | 2. Make sure you have recent [Docker](https://www.docker.com/) installed.
73 | 3. Browsers that will be used described in _config/browsers.json_. Chrome and Firefox images included into _docker_compose.yml_. If you need to update browsers, edit _config/browsers.json_ (instructions could be found [here](https://aerokube.com/selenoid/latest/#_browsers_configuration_file), list of available images is [here](https://aerokube.com/selenoid/latest/#_browser_image_information)). Download browser images manually (`docker pull image` where `image` specified in _config/browsers.json_) or automatically using [jq](https://stedolan.github.io/jq/download/) tool. See execution command in _update_browsers.sh_.
74 | 4. If you're running docker on Windows, be sure to update _docker-compose.yml_ (uncomment Windows-related `postgres.volumes` section and `volumes` at the end of the file) for correct ReportPortal work.
75 | 5. Run `docker-compose up --force-recreate -d` to start Selenoid, Selenoid UI and ReportPortal.
76 |
77 | **Execution**
78 | 1. If you want to use Selenoid as a hub, update `selenoid.hub.address` in _pom.xml_ with your actual IP address (`docker inspect -f '{{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}' container_name_or_id`). If you want to use built-in WebDriverManager just set parameter `selenoid.enabled` to false.
79 | 2. Edit _source/test/resources/testng.xml_ to include or exclude your test-cases.
80 | 3. Setup RP:
81 | - navigate to ReportPortal instance (by default [http://localhost:8080](http://localhost:8080))
82 | - login as administrator (superadmin/erebus)
83 | - click on arrow menu icon in a top-right corner and select Administrate
84 | - click Add New Project button
85 | - set name of your project and all other settings
86 | - click Back to Project button in top-right corner
87 | - select your newly created project from dropdown in top-left corner
88 | - select Profile from dropdown in top-right corner
89 | - copy configuration properties to your _reportportal.properties_
90 | 4. Run `mvn test` command to execute whole suite.
91 |
92 | **Results**
93 | - to check in-browser execution, navigate to your Selenoid-UI instance (e.g. [localhost:8090](http://localhost:8090))
94 | - to check reports, navigate to ReportPortal instance (e.g. [localhost:8080](http://localhost:8080))
95 |
--------------------------------------------------------------------------------
/src/main/java/automation/builders/AddressBuilder.java:
--------------------------------------------------------------------------------
1 | package automation.builders;
2 |
3 | import automation.entities.Address;
4 | import com.github.javafaker.Faker;
5 |
6 | import java.util.HashMap;
7 | import java.util.Locale;
8 | import java.util.Map;
9 |
10 | public class AddressBuilder {
11 | private String line1;
12 | private String line2;
13 | private String city;
14 | private String state;
15 | private String zip;
16 | private String country;
17 |
18 | public AddressBuilder() {
19 | Faker faker = new Faker(new Locale("en", "US"));
20 | this.line1 = faker.address().streetAddress();
21 | this.line2 = faker.address().secondaryAddress();
22 | this.city = faker.address().city();
23 | this.state = faker.address().state();
24 | String stateAbbreviation = getStateAbbreviation(state);
25 | this.zip = faker.address().zipCodeByState(stateAbbreviation);
26 | this.country = "United States";
27 | }
28 |
29 | public String getLine1() {
30 | return line1;
31 | }
32 |
33 | public AddressBuilder setLine1(String line1) {
34 | this.line1 = line1;
35 | return this;
36 | }
37 |
38 | public String getLine2() {
39 | return line2;
40 | }
41 |
42 | public AddressBuilder setLine2(String line2) {
43 | this.line2 = line2;
44 | return this;
45 | }
46 |
47 | public String getCity() {
48 | return city;
49 | }
50 |
51 | public AddressBuilder setCity(String city) {
52 | this.city = city;
53 | return this;
54 | }
55 |
56 | public String getState() {
57 | return state;
58 | }
59 |
60 | public AddressBuilder setState(String state) {
61 | this.state = state;
62 | return this;
63 | }
64 |
65 | public String getZip() {
66 | return zip;
67 | }
68 |
69 | public AddressBuilder setZip(String zip) {
70 | this.zip = zip;
71 | return this;
72 | }
73 |
74 | public String getCountry() {
75 | return country;
76 | }
77 |
78 | public AddressBuilder setCountry(String country) {
79 | this.country = country;
80 | return this;
81 | }
82 |
83 | public Address build() {
84 | return new Address(this);
85 | }
86 |
87 | private String getStateAbbreviation(String state) {
88 | Map states = new HashMap<>();
89 | states.put("Alabama", "AL");
90 | states.put("Alaska", "AK");
91 | states.put("Alberta", "AB");
92 | states.put("American Samoa", "AS");
93 | states.put("Arizona", "AZ");
94 | states.put("Arkansas", "AR");
95 | states.put("Armed Forces (AE)", "AE");
96 | states.put("Armed Forces Americas", "AA");
97 | states.put("Armed Forces Pacific", "AP");
98 | states.put("British Columbia", "BC");
99 | states.put("California", "CA");
100 | states.put("Colorado", "CO");
101 | states.put("Connecticut", "CT");
102 | states.put("Delaware", "DE");
103 | states.put("District Of Columbia", "DC");
104 | states.put("Florida", "FL");
105 | states.put("Georgia", "GA");
106 | states.put("Guam", "GU");
107 | states.put("Hawaii", "HI");
108 | states.put("Idaho", "ID");
109 | states.put("Illinois", "IL");
110 | states.put("Indiana", "IN");
111 | states.put("Iowa", "IA");
112 | states.put("Kansas", "KS");
113 | states.put("Kentucky", "KY");
114 | states.put("Louisiana", "LA");
115 | states.put("Maine", "ME");
116 | states.put("Manitoba", "MB");
117 | states.put("Maryland", "MD");
118 | states.put("Massachusetts", "MA");
119 | states.put("Michigan", "MI");
120 | states.put("Minnesota", "MN");
121 | states.put("Mississippi", "MS");
122 | states.put("Missouri", "MO");
123 | states.put("Montana", "MT");
124 | states.put("Nebraska", "NE");
125 | states.put("Nevada", "NV");
126 | states.put("New Brunswick", "NB");
127 | states.put("New Hampshire", "NH");
128 | states.put("New Jersey", "NJ");
129 | states.put("New Mexico", "NM");
130 | states.put("New York", "NY");
131 | states.put("Newfoundland", "NF");
132 | states.put("North Carolina", "NC");
133 | states.put("North Dakota", "ND");
134 | states.put("Northwest Territories", "NT");
135 | states.put("Nova Scotia", "NS");
136 | states.put("Nunavut", "NU");
137 | states.put("Ohio", "OH");
138 | states.put("Oklahoma", "OK");
139 | states.put("Ontario", "ON");
140 | states.put("Oregon", "OR");
141 | states.put("Pennsylvania", "PA");
142 | states.put("Prince Edward Island", "PE");
143 | states.put("Puerto Rico", "PR");
144 | states.put("Quebec", "QC");
145 | states.put("Rhode Island", "RI");
146 | states.put("Saskatchewan", "SK");
147 | states.put("South Carolina", "SC");
148 | states.put("South Dakota", "SD");
149 | states.put("Tennessee", "TN");
150 | states.put("Texas", "TX");
151 | states.put("Utah", "UT");
152 | states.put("Vermont", "VT");
153 | states.put("Virgin Islands", "VI");
154 | states.put("Virginia", "VA");
155 | states.put("Washington", "WA");
156 | states.put("West Virginia", "WV");
157 | states.put("Wisconsin", "WI");
158 | states.put("Wyoming", "WY");
159 | states.put("Yukon Territory", "YT");
160 |
161 | return states.get(state);
162 | }
163 | }
--------------------------------------------------------------------------------
/pom.xml:
--------------------------------------------------------------------------------
1 |
2 |
5 | 4.0.0
6 |
7 | com.d3m0
8 | automation
9 | 1.0-SNAPSHOT
10 |
11 |
12 | 11
13 | false
14 |
15 | http://localhost:4444
16 | http://automationpractice.com/
17 |
18 |
19 |
20 | UTF-8
21 |
22 |
23 |
24 |
25 | org.seleniumhq.selenium
26 | selenium-java
27 | 3.141.59
28 |
29 |
30 |
31 | org.testng
32 | testng
33 | 7.4.0
34 |
35 |
36 |
37 | org.apache.logging.log4j
38 | log4j-api
39 | 2.14.1
40 |
41 |
42 |
43 | org.apache.logging.log4j
44 | log4j-slf4j-impl
45 | 2.14.1
46 | test
47 |
48 |
49 |
50 | com.codeborne
51 | selenide
52 | 5.20.4
53 |
54 |
55 |
56 | com.epam.reportportal
57 | agent-java-testng
58 | 5.0.11
59 |
60 |
61 | com.epam.reportportal
62 | logger-java-log4j
63 | 5.0.3
64 |
65 |
66 | com.github.javafaker
67 | javafaker
68 | 1.0.2
69 |
70 |
71 | ru.yandex.qatools.ashot
72 | ashot
73 | 1.5.4
74 |
75 |
76 | org.projectlombok
77 | lombok
78 | 1.18.20
79 |
80 |
81 |
82 |
83 |
84 | org.apache.maven.plugins
85 | maven-compiler-plugin
86 | 3.8.1
87 |
88 | ${java.version}
89 | ${java.version}
90 |
91 |
92 |
93 | org.apache.maven.plugins
94 | maven-surefire-plugin
95 | 2.22.1
96 |
97 | methods
98 | 10
99 |
100 |
101 | usedefaultlisteners
102 | false
103 |
104 |
105 | listener
106 | automation.utils.MyListener
107 |
108 |
109 | suitethreadpoolsize
110 | 2
111 |
112 |
113 | true
114 |
115 | ${selenoid.enabled}
116 | ${selenoid.hub.address}
117 | ${project.build.directory}
118 | false
119 | ${url.address}
120 | ${browser.resolution}
121 |
122 |
123 |
124 | src/test/resources/testng.xml
125 | src/test/resources/failing.xml
126 |
127 |
128 |
129 |
130 |
131 |
132 |
--------------------------------------------------------------------------------
/docker-compose.yml:
--------------------------------------------------------------------------------
1 | version: "2.4"
2 | services:
3 | selenoid:
4 | network_mode: bridge
5 | image: aerokube/selenoid:latest-release
6 | command: ["-conf", "/etc/selenoid/browsers.json", "-limit", "10", "-service-startup-timeout", "3m", "-session-delete-timeout", "3m"]
7 | ports:
8 | - 4444:4444
9 | volumes:
10 | - ./config:/etc/selenoid
11 | - /var/run/docker.sock:/var/run/docker.sock
12 | container_name: selenoid
13 |
14 | selenoid-ui:
15 | network_mode: bridge
16 | image: aerokube/selenoid-ui:latest-release
17 | command: ["--selenoid-uri", "http://selenoid:4444"]
18 | ports:
19 | - 8090:8080
20 | links:
21 | - selenoid
22 | container_name: selenoid-ui
23 |
24 | chrome:
25 | image: selenoid/vnc:chrome_90.0
26 | container_name: chrome_90.0
27 |
28 | firefox:
29 | image: selenoid/vnc:firefox_87.0
30 | container_name: firefox_87.0
31 |
32 | gateway:
33 | image: traefik:v2.0.5
34 | ports:
35 | - "8080:8080" # HTTP exposed
36 | - "8081:8081" # HTTP Administration exposed
37 | volumes:
38 | - /var/run/docker.sock:/var/run/docker.sock
39 | command:
40 | - --providers.docker=true
41 | - --providers.docker.constraints=Label(`traefik.expose`, `true`)
42 | - --entrypoints.web.address=:8080
43 | - --entrypoints.traefik.address=:8081
44 | - --api.dashboard=true
45 | - --api.insecure=true
46 | restart: always
47 |
48 | elasticsearch:
49 | image: docker.elastic.co/elasticsearch/elasticsearch:7.3.0
50 | volumes:
51 | - ./data/elasticsearch:/usr/share/elasticsearch/data
52 | environment:
53 | - "bootstrap.memory_lock=true"
54 | - "discovery.type=single-node"
55 | - "logger.level=INFO"
56 | - "xpack.security.enabled=true"
57 | - "ELASTIC_PASSWORD=elastic1q2w3e"
58 | ulimits:
59 | memlock:
60 | soft: -1
61 | hard: -1
62 | nofile:
63 | soft: 65536
64 | hard: 65536
65 | # ports:
66 | # - "9200:9200"
67 | healthcheck:
68 | test: ["CMD", "curl","-s" ,"-f", "http://localhost:9200/_cat/health"]
69 | restart: always
70 |
71 | analyzer:
72 | image: reportportal/service-auto-analyzer:5.3.1
73 | environment:
74 | LOGGING_LEVEL: info
75 | AMQP_URL: amqp://rabbitmq:rabbitmq@rabbitmq:5672
76 | ES_HOSTS: http://elastic:elastic1q2w3e@elasticsearch:9200
77 | depends_on:
78 | - elasticsearch
79 | restart: always
80 |
81 | ### Initial reportportal db schema. Run once.
82 | db-scripts:
83 | image: reportportal/migrations:5.3.0
84 | depends_on:
85 | postgres:
86 | condition: service_healthy
87 | environment:
88 | POSTGRES_SERVER: postgres
89 | POSTGRES_PORT: 5432
90 | POSTGRES_DB: reportportal
91 | POSTGRES_USER: rpuser
92 | POSTGRES_PASSWORD: rppass
93 | restart: on-failure
94 |
95 | postgres:
96 | image: postgres:12-alpine
97 | shm_size: '512m'
98 | environment:
99 | POSTGRES_USER: rpuser
100 | POSTGRES_PASSWORD: rppass
101 | POSTGRES_DB: reportportal
102 | volumes:
103 | # For unix host
104 | - ./data/postgres:/var/lib/postgresql/data
105 | # For windows host
106 | # - postgres:/var/lib/postgresql/data
107 | # If you need to access the DB locally. Could be a security risk to expose DB.
108 | # ports:
109 | # - "5432:5432"
110 | command:
111 | -c checkpoint_completion_target=0.9
112 | -c work_mem=96MB
113 | -c wal_writer_delay=20ms
114 | -c synchronous_commit=off
115 | -c wal_buffers=32MB
116 | -c min_wal_size=2GB
117 | -c max_wal_size=4GB
118 | # Optional, for SSD Data Storage. If you are using the HDD, set up this command to '2'
119 | # -c effective_io_concurrency=200
120 | # Optional, for SSD Data Storage. If you are using the HDD, set up this command to '4'
121 | # -c random_page_cost=1.1
122 | # Optional, can be scaled. Example for 4 CPU, 16GB RAM instance, where only the database is deployed
123 | # -c max_worker_processes=4
124 | # -c max_parallel_workers_per_gather=2
125 | # -c max_parallel_workers=4
126 | # -c shared_buffers=4GB
127 | # -c effective_cache_size=12GB
128 | # -c maintenance_work_mem=1GB
129 | healthcheck:
130 | test: ["CMD-SHELL", "pg_isready -d $$POSTGRES_DB -U $$POSTGRES_USER"]
131 | interval: 10s
132 | timeout: 120s
133 | retries: 10
134 | restart: always
135 |
136 | rabbitmq:
137 | image: rabbitmq:3.7.16-management
138 | #ports:
139 | # - "5672:5672"
140 | #- "15672:15672"
141 | environment:
142 | RABBITMQ_DEFAULT_USER: "rabbitmq"
143 | RABBITMQ_DEFAULT_PASS: "rabbitmq"
144 | healthcheck:
145 | test: ["CMD", "rabbitmqctl", "status"]
146 | retries: 5
147 | restart: always
148 |
149 | uat:
150 | image: reportportal/service-authorization:5.3.5
151 | #ports:
152 | # - "9999:9999"
153 | environment:
154 | - RP_DB_HOST=postgres
155 | - RP_DB_USER=rpuser
156 | - RP_DB_PASS=rppass
157 | - RP_DB_NAME=reportportal
158 | - RP_BINARYSTORE_TYPE=minio
159 | - RP_BINARYSTORE_MINIO_ENDPOINT=http://minio:9000
160 | - RP_BINARYSTORE_MINIO_ACCESSKEY=minio
161 | - RP_BINARYSTORE_MINIO_SECRETKEY=minio123
162 | - RP_SESSION_LIVE=86400 #in seconds
163 | labels:
164 | - "traefik.http.middlewares.uat-strip-prefix.stripprefix.prefixes=/uat"
165 | - "traefik.http.routers.uat.middlewares=uat-strip-prefix@docker"
166 | - "traefik.http.routers.uat.rule=PathPrefix(`/uat`)"
167 | - "traefik.http.routers.uat.service=uat"
168 | - "traefik.http.services.uat.loadbalancer.server.port=9999"
169 | - "traefik.http.services.uat.loadbalancer.server.scheme=http"
170 | - "traefik.expose=true"
171 | restart: always
172 |
173 | index:
174 | image: reportportal/service-index:5.0.10
175 | depends_on:
176 | gateway:
177 | condition: service_started
178 | environment:
179 | - LB_URL=http://gateway:8081
180 | - TRAEFIK_V2_MODE=true
181 | labels:
182 | - "traefik.http.routers.index.rule=PathPrefix(`/`)"
183 | - "traefik.http.routers.index.service=index"
184 | - "traefik.http.services.index.loadbalancer.server.port=8080"
185 | - "traefik.http.services.index.loadbalancer.server.scheme=http"
186 | - "traefik.expose=true"
187 | restart: always
188 |
189 | api:
190 | image: reportportal/service-api:5.3.5
191 | depends_on:
192 | rabbitmq:
193 | condition: service_healthy
194 | gateway:
195 | condition: service_started
196 | postgres:
197 | condition: service_healthy
198 | environment:
199 | - RP_DB_HOST=postgres
200 | - RP_DB_USER=rpuser
201 | - RP_DB_PASS=rppass
202 | - RP_DB_NAME=reportportal
203 | - RP_AMQP_USER=rabbitmq
204 | - RP_AMQP_PASS=rabbitmq
205 | - RP_AMQP_APIUSER=rabbitmq
206 | - RP_AMQP_APIPASS=rabbitmq
207 | - RP_BINARYSTORE_TYPE=minio
208 | - RP_BINARYSTORE_MINIO_ENDPOINT=http://minio:9000
209 | - RP_BINARYSTORE_MINIO_ACCESSKEY=minio
210 | - RP_BINARYSTORE_MINIO_SECRETKEY=minio123
211 | - LOGGING_LEVEL_ORG_HIBERNATE_SQL=info
212 | - JAVA_OPTS=-Xmx1g -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/tmp -Dcom.sun.management.jmxremote.rmi.port=12349 -Dcom.sun.management.jmxremote -Dcom.sun.management.jmxremote.local.only=false -Dcom.sun.management.jmxremote.port=9010 -Dcom.sun.management.jmxremote.authenticate=false -Dcom.sun.management.jmxremote.ssl=false -Djava.rmi.server.hostname=0.0.0.0
213 | labels:
214 | - "traefik.http.middlewares.api-strip-prefix.stripprefix.prefixes=/api"
215 | - "traefik.http.routers.api.middlewares=api-strip-prefix@docker"
216 | - "traefik.http.routers.api.rule=PathPrefix(`/api`)"
217 | - "traefik.http.routers.api.service=api"
218 | - "traefik.http.services.api.loadbalancer.server.port=8585"
219 | - "traefik.http.services.api.loadbalancer.server.scheme=http"
220 | - "traefik.expose=true"
221 | restart: always
222 |
223 | ui:
224 | image: reportportal/service-ui:5.3.5
225 | environment:
226 | - RP_SERVER_PORT=8080
227 | labels:
228 | - "traefik.http.middlewares.ui-strip-prefix.stripprefix.prefixes=/ui"
229 | - "traefik.http.routers.ui.middlewares=ui-strip-prefix@docker"
230 | - "traefik.http.routers.ui.rule=PathPrefix(`/ui`)"
231 | - "traefik.http.routers.ui.service=ui"
232 | - "traefik.http.services.ui.loadbalancer.server.port=8080"
233 | - "traefik.http.services.ui.loadbalancer.server.scheme=http"
234 | - "traefik.expose=true"
235 | restart: always
236 |
237 | minio:
238 | image: minio/minio:RELEASE.2020-05-01T22-19-14Z
239 | #ports:
240 | # - '9000:9000'
241 | volumes:
242 | - ./data/storage:/data
243 | environment:
244 | MINIO_ACCESS_KEY: minio
245 | MINIO_SECRET_KEY: minio123
246 | command: server /data
247 | healthcheck:
248 | test: ["CMD", "curl", "-f", "http://localhost:9000/minio/health/live"]
249 | interval: 30s
250 | timeout: 20s
251 | retries: 3
252 | restart: always
253 |
254 | # Docker volume for Windows host
255 | # volumes:
256 | # postgres:
257 |
--------------------------------------------------------------------------------