├── 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 | --------------------------------------------------------------------------------