diff --git a/localenv.sh b/localenv.sh index 4d35dcdce..a12ddacd8 100644 --- a/localenv.sh +++ b/localenv.sh @@ -3,7 +3,7 @@ docker stop $(docker ps -a -q) docker rm $(docker ps -a -q) -docker run --name niffler-all -p 5432:5432 -e POSTGRES_PASSWORD=secret -e CREATE_DATABASES=niffler-auth,niffler-currency,niffler-spend,niffler-userdata -v pgdata:/var/lib/postgresql/data -v ./postgres/init-database.sh:/docker-entrypoint-initdb.d/init-database.sh -d postgres:15.1 --max_prepared_transactions=100 +docker run --name niffler-all -p 5432:5432 -e POSTGRES_PASSWORD=secret -e CREATE_DATABASES=niffler-auth,niffler-currency,niffler-spend,niffler-userdata -v pgdata:/var/lib/postgresql/data -v /Users/oleg/Developer/niffler-ng-6/postgres/init-database.sh:/docker-entrypoint-initdb.d/init-database.sh -d postgres:15.1 --max_prepared_transactions=100 docker run --name=zookeeper -e ZOOKEEPER_CLIENT_PORT=2181 -p 2181:2181 -d confluentinc/cp-zookeeper:7.3.2 docker run --name=kafka -e KAFKA_BROKER_ID=1 \ -e KAFKA_ZOOKEEPER_CONNECT=$(docker inspect zookeeper --format='{{ .NetworkSettings.IPAddress }}'):2181 \ diff --git a/niffler-e-2-e-tests/src/test/java/guru/qa/niffler/api/GhApi.java b/niffler-e-2-e-tests/src/test/java/guru/qa/niffler/api/GhApi.java index b6257d750..45bd9514f 100644 --- a/niffler-e-2-e-tests/src/test/java/guru/qa/niffler/api/GhApi.java +++ b/niffler-e-2-e-tests/src/test/java/guru/qa/niffler/api/GhApi.java @@ -8,8 +8,7 @@ import retrofit2.http.Path; public interface GhApi { - - @GET("repos/qa-guru/niffler/issues/{issue_number}") + @GET("repos/tankisleva/java_pft/issues/{issue_number}") @Headers({ "Accept: application/vnd.github+json", "X-GitHub-Api-Version: 2022-11-28" diff --git a/niffler-e-2-e-tests/src/test/java/guru/qa/niffler/api/SpendApi.java b/niffler-e-2-e-tests/src/test/java/guru/qa/niffler/api/SpendApi.java index 0d4a987c1..b8fb86da8 100644 --- a/niffler-e-2-e-tests/src/test/java/guru/qa/niffler/api/SpendApi.java +++ b/niffler-e-2-e-tests/src/test/java/guru/qa/niffler/api/SpendApi.java @@ -1,12 +1,50 @@ package guru.qa.niffler.api; +import guru.qa.niffler.model.CategoryJson; +import guru.qa.niffler.model.CurrencyValues; import guru.qa.niffler.model.SpendJson; import retrofit2.Call; -import retrofit2.http.Body; -import retrofit2.http.POST; +import retrofit2.http.*; + +import java.util.Date; +import java.util.List; public interface SpendApi { @POST("internal/spends/add") Call addSpend(@Body SpendJson spend); + + @PATCH("internal/spends/edit") + Call editSpend(@Body SpendJson spend); + + @GET("internal/spends/{id}") + Call getSpend(@Path("id") String id, + @Query("username") String username); + + @GET("internal/spends/all") + Call> getSpends( + @Query("username") String username, + @Query("filterCurrency") CurrencyValues filterCurrency, + @Query("from") String from, + @Query("to") String to + ); + + @DELETE("internal/spends/remove") + Call deleteSpends( + @Query("username") String username, + @Query("ids") List ids + ); + + @POST("internal/categories/add") + Call addCategory(@Body CategoryJson category); + + @PATCH("internal/categories/update") + Call updateCategory(@Body CategoryJson category); + + @GET("internal/categories/all") + Call> getCategories( + @Query("username") String username, + @Query("excludeArchived") boolean excludeArchived + ); + } diff --git a/niffler-e-2-e-tests/src/test/java/guru/qa/niffler/api/SpendApiClient.java b/niffler-e-2-e-tests/src/test/java/guru/qa/niffler/api/SpendApiClient.java index f40ebfcbe..379bf09a7 100644 --- a/niffler-e-2-e-tests/src/test/java/guru/qa/niffler/api/SpendApiClient.java +++ b/niffler-e-2-e-tests/src/test/java/guru/qa/niffler/api/SpendApiClient.java @@ -1,11 +1,20 @@ package guru.qa.niffler.api; import guru.qa.niffler.config.Config; +import guru.qa.niffler.model.CategoryJson; +import guru.qa.niffler.model.CurrencyValues; import guru.qa.niffler.model.SpendJson; import lombok.SneakyThrows; +import retrofit2.Response; import retrofit2.Retrofit; import retrofit2.converter.jackson.JacksonConverterFactory; +import java.io.IOException; +import java.util.Date; +import java.util.List; + +import static org.junit.jupiter.api.Assertions.assertEquals; + public class SpendApiClient { private final Retrofit retrofit = new Retrofit.Builder() @@ -15,10 +24,98 @@ public class SpendApiClient { private final SpendApi spendApi = retrofit.create(SpendApi.class); - @SneakyThrows public SpendJson createSpend(SpendJson spend) { - return spendApi.addSpend(spend) - .execute() - .body(); + final Response response; + try { + response = spendApi.addSpend(spend) + .execute(); + } catch (IOException e) { + throw new AssertionError(e); + } + assertEquals(201, response.code()); + return response.body(); + } + + public SpendJson editSpend(SpendJson spend) { + final Response response; + try { + response = spendApi.editSpend(spend) + .execute(); + } catch (IOException e) { + throw new AssertionError(e); + } + assertEquals(200, response.code()); + return response.body(); + } + + public SpendJson getSpend(String id, String username) { + final Response response; + try { + response = spendApi.getSpend(id, username) + .execute(); + } catch (IOException e) { + throw new AssertionError(e); + } + assertEquals(200, response.code()); + return response.body(); + } + + public List getSpends(String username, CurrencyValues filterCurrency, String from, String to) { + final Response> response; + try { + response = spendApi.getSpends(username, filterCurrency, from, to) + .execute(); + } catch (IOException e) { + throw new AssertionError(e); + } + assertEquals(200, response.code()); + return response.body(); + } + + public void deleteSpends(String username, List ids) { + Response response; + try { + response = spendApi.deleteSpends(username, ids) + .execute(); + } catch (IOException e) { + throw new AssertionError(e); + } + assertEquals(202, response.code()); + } + + public CategoryJson addCategory(CategoryJson category) { + final Response response; + try { + response = spendApi.addCategory(category) + .execute(); + } catch (IOException e) { + throw new AssertionError(e); + } + assertEquals(200, response.code()); + return response.body(); + } + + public CategoryJson updateCategory(CategoryJson category) { + final Response response; + try { + response = spendApi.updateCategory(category) + .execute(); + } catch (IOException e) { + throw new AssertionError(e); + } + assertEquals(200, response.code()); + return response.body(); + } + + public List getCategories(String username, boolean excludeArchived) { + final Response> response; + try { + response = spendApi.getCategories(username, excludeArchived) + .execute(); + } catch (IOException e) { + throw new AssertionError(e); + } + assertEquals(200, response.code()); + return response.body(); } } diff --git a/niffler-e-2-e-tests/src/test/java/guru/qa/niffler/jupiter/annotation/Category.java b/niffler-e-2-e-tests/src/test/java/guru/qa/niffler/jupiter/annotation/Category.java new file mode 100644 index 000000000..ecbad9498 --- /dev/null +++ b/niffler-e-2-e-tests/src/test/java/guru/qa/niffler/jupiter/annotation/Category.java @@ -0,0 +1,18 @@ +package guru.qa.niffler.jupiter.annotation; + +import guru.qa.niffler.jupiter.extension.AddCategoryExtension; +import org.junit.jupiter.api.extension.ExtendWith; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.METHOD) +@ExtendWith(AddCategoryExtension.class) +public @interface Category { + String username(); + boolean isArchive(); + String name() default ""; +} \ No newline at end of file diff --git a/niffler-e-2-e-tests/src/test/java/guru/qa/niffler/jupiter/annotation/Spending.java b/niffler-e-2-e-tests/src/test/java/guru/qa/niffler/jupiter/annotation/Spending.java index df11b002c..921267095 100644 --- a/niffler-e-2-e-tests/src/test/java/guru/qa/niffler/jupiter/annotation/Spending.java +++ b/niffler-e-2-e-tests/src/test/java/guru/qa/niffler/jupiter/annotation/Spending.java @@ -1,6 +1,7 @@ package guru.qa.niffler.jupiter.annotation; -import guru.qa.niffler.jupiter.extension.SpendingExtension; +import guru.qa.niffler.jupiter.extension.CreateSpendingExtension; +import guru.qa.niffler.jupiter.extension.SpendingResolverExtension; import org.junit.jupiter.api.extension.ExtendWith; import java.lang.annotation.ElementType; @@ -10,7 +11,7 @@ @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.METHOD) -@ExtendWith(SpendingExtension.class) +@ExtendWith({CreateSpendingExtension.class, SpendingResolverExtension.class}) public @interface Spending { String username(); diff --git a/niffler-e-2-e-tests/src/test/java/guru/qa/niffler/jupiter/extension/AddCategoryExtension.java b/niffler-e-2-e-tests/src/test/java/guru/qa/niffler/jupiter/extension/AddCategoryExtension.java new file mode 100644 index 000000000..23c236ee0 --- /dev/null +++ b/niffler-e-2-e-tests/src/test/java/guru/qa/niffler/jupiter/extension/AddCategoryExtension.java @@ -0,0 +1,71 @@ +package guru.qa.niffler.jupiter.extension; + +import com.github.javafaker.Faker; +import guru.qa.niffler.api.SpendApiClient; +import guru.qa.niffler.jupiter.annotation.Category; +import guru.qa.niffler.model.CategoryJson; +import org.junit.jupiter.api.extension.*; +import org.junit.platform.commons.support.AnnotationSupport; + +public class AddCategoryExtension implements + BeforeEachCallback, + AfterTestExecutionCallback, + ParameterResolver { + + public static final ExtensionContext.Namespace NAMESPACE = ExtensionContext.Namespace.create(AddCategoryExtension.class); + private final SpendApiClient spendApiClient = new SpendApiClient(); + private final Faker faker = new Faker(); + + @Override + public void beforeEach(ExtensionContext context) { + AnnotationSupport.findAnnotation(context.getRequiredTestMethod(), Category.class) + .ifPresent(anno ->{ + String name = faker.backToTheFuture().quote(); + CategoryJson categoryJson = new CategoryJson( + null, + anno.name().isEmpty() ? name : anno.name(), + anno.username(), + false + ); + + CategoryJson createCategory = spendApiClient.addCategory(categoryJson); + + if (anno.isArchive()) { + CategoryJson archivedCategory = new CategoryJson( + createCategory.id(), + createCategory.name(), + createCategory.username(), + true + ); + createCategory = spendApiClient.updateCategory(archivedCategory); + } + + context.getStore(NAMESPACE).put(context.getUniqueId(), createCategory); + }); + } + + @Override + public boolean supportsParameter(ParameterContext parameterContext, ExtensionContext extensionContext) throws ParameterResolutionException { + return parameterContext.getParameter().getType().isAssignableFrom(CategoryJson.class); + } + + @Override + public Object resolveParameter(ParameterContext parameterContext, ExtensionContext extensionContext) throws ParameterResolutionException { + return extensionContext.getStore(AddCategoryExtension.NAMESPACE).get(extensionContext.getUniqueId(), CategoryJson.class); + } + + @Override + public void afterTestExecution(ExtensionContext context) { + CategoryJson categoryJson = context.getStore(NAMESPACE).get(context.getUniqueId(), CategoryJson.class); + + if (categoryJson.archived()) { + CategoryJson archivedCategory = new CategoryJson( + categoryJson.id(), + categoryJson.name(), + categoryJson.username(), + true + ); + spendApiClient.updateCategory(archivedCategory); + } + } +} \ No newline at end of file diff --git a/niffler-e-2-e-tests/src/test/java/guru/qa/niffler/jupiter/extension/BrowserExtension.java b/niffler-e-2-e-tests/src/test/java/guru/qa/niffler/jupiter/extension/BrowserExtension.java index af20fdf49..d26f10d19 100644 --- a/niffler-e-2-e-tests/src/test/java/guru/qa/niffler/jupiter/extension/BrowserExtension.java +++ b/niffler-e-2-e-tests/src/test/java/guru/qa/niffler/jupiter/extension/BrowserExtension.java @@ -5,15 +5,15 @@ import com.codeborne.selenide.logevents.SelenideLogger; import io.qameta.allure.Allure; import io.qameta.allure.selenide.AllureSelenide; -import org.junit.jupiter.api.extension.AfterEachCallback; -import org.junit.jupiter.api.extension.BeforeEachCallback; -import org.junit.jupiter.api.extension.ExtensionContext; -import org.junit.jupiter.api.extension.LifecycleMethodExecutionExceptionHandler; -import org.junit.jupiter.api.extension.TestExecutionExceptionHandler; +import org.junit.jupiter.api.extension.*; import org.openqa.selenium.OutputType; import org.openqa.selenium.TakesScreenshot; import java.io.ByteArrayInputStream; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; public class BrowserExtension implements BeforeEachCallback, @@ -64,4 +64,5 @@ private static void doScreenshot() { ); } } + } diff --git a/niffler-e-2-e-tests/src/test/java/guru/qa/niffler/jupiter/extension/CreateSpendingExtension.java b/niffler-e-2-e-tests/src/test/java/guru/qa/niffler/jupiter/extension/CreateSpendingExtension.java new file mode 100644 index 000000000..1ecb6fdc2 --- /dev/null +++ b/niffler-e-2-e-tests/src/test/java/guru/qa/niffler/jupiter/extension/CreateSpendingExtension.java @@ -0,0 +1,44 @@ +package guru.qa.niffler.jupiter.extension; + +import guru.qa.niffler.api.SpendApiClient; +import guru.qa.niffler.jupiter.annotation.Spending; +import guru.qa.niffler.model.CategoryJson; +import guru.qa.niffler.model.CurrencyValues; +import guru.qa.niffler.model.SpendJson; +import org.junit.jupiter.api.extension.BeforeEachCallback; +import org.junit.jupiter.api.extension.ExtensionContext; +import org.junit.platform.commons.support.AnnotationSupport; + +import java.util.Date; + +public class CreateSpendingExtension implements BeforeEachCallback { + + public static final ExtensionContext.Namespace NAMESPACE = ExtensionContext.Namespace.create(CreateSpendingExtension.class); + + private final SpendApiClient spendApiClient = new SpendApiClient(); + + @Override + public void beforeEach(ExtensionContext context) throws Exception { + AnnotationSupport.findAnnotation(context.getRequiredTestMethod(), Spending.class) + .ifPresent(anno -> { + SpendJson spend = new SpendJson( + null, + new Date(), + new CategoryJson( + null, + anno.category(), + anno.username(), + false + ), + CurrencyValues.RUB, + anno.amount(), + anno.description(), + anno.username() + ); + context.getStore(NAMESPACE).put( + context.getUniqueId(), + spendApiClient.createSpend(spend) + ); + }); + } +} \ No newline at end of file diff --git a/niffler-e-2-e-tests/src/test/java/guru/qa/niffler/jupiter/extension/SpendingExtension.java b/niffler-e-2-e-tests/src/test/java/guru/qa/niffler/jupiter/extension/SpendingExtension.java deleted file mode 100644 index 731f2c84f..000000000 --- a/niffler-e-2-e-tests/src/test/java/guru/qa/niffler/jupiter/extension/SpendingExtension.java +++ /dev/null @@ -1,57 +0,0 @@ -package guru.qa.niffler.jupiter.extension; - -import guru.qa.niffler.api.SpendApiClient; -import guru.qa.niffler.jupiter.annotation.Spending; -import guru.qa.niffler.model.CategoryJson; -import guru.qa.niffler.model.CurrencyValues; -import guru.qa.niffler.model.SpendJson; -import org.junit.jupiter.api.extension.BeforeEachCallback; -import org.junit.jupiter.api.extension.ExtensionContext; -import org.junit.jupiter.api.extension.ParameterContext; -import org.junit.jupiter.api.extension.ParameterResolutionException; -import org.junit.jupiter.api.extension.ParameterResolver; -import org.junit.platform.commons.support.AnnotationSupport; - -import java.util.Date; - -public class SpendingExtension implements BeforeEachCallback, ParameterResolver { - - public static final ExtensionContext.Namespace NAMESPACE = ExtensionContext.Namespace.create(SpendingExtension.class); - - private final SpendApiClient spendApiClient = new SpendApiClient(); - - @Override - public void beforeEach(ExtensionContext context) throws Exception { - AnnotationSupport.findAnnotation(context.getRequiredTestMethod(), Spending.class) - .ifPresent(anno -> { - SpendJson spend = new SpendJson( - null, - new Date(), - new CategoryJson( - null, - anno.category(), - anno.username(), - false - ), - CurrencyValues.RUB, - anno.amount(), - anno.description(), - anno.username() - ); - context.getStore(NAMESPACE).put( - context.getUniqueId(), - spendApiClient.createSpend(spend) - ); - }); - } - - @Override - public boolean supportsParameter(ParameterContext parameterContext, ExtensionContext extensionContext) throws ParameterResolutionException { - return parameterContext.getParameter().getType().isAssignableFrom(SpendJson.class); - } - - @Override - public SpendJson resolveParameter(ParameterContext parameterContext, ExtensionContext extensionContext) throws ParameterResolutionException { - return extensionContext.getStore(NAMESPACE).get(extensionContext.getUniqueId(), SpendJson.class); - } -} diff --git a/niffler-e-2-e-tests/src/test/java/guru/qa/niffler/jupiter/extension/SpendingResolverExtension.java b/niffler-e-2-e-tests/src/test/java/guru/qa/niffler/jupiter/extension/SpendingResolverExtension.java new file mode 100644 index 000000000..63b9e6d99 --- /dev/null +++ b/niffler-e-2-e-tests/src/test/java/guru/qa/niffler/jupiter/extension/SpendingResolverExtension.java @@ -0,0 +1,21 @@ +package guru.qa.niffler.jupiter.extension; + +import guru.qa.niffler.model.SpendJson; +import org.junit.jupiter.api.extension.ExtensionContext; +import org.junit.jupiter.api.extension.ParameterContext; +import org.junit.jupiter.api.extension.ParameterResolutionException; +import org.junit.jupiter.api.extension.ParameterResolver; + + +public class SpendingResolverExtension implements ParameterResolver { + + @Override + public boolean supportsParameter(ParameterContext parameterContext, ExtensionContext extensionContext) throws ParameterResolutionException { + return parameterContext.getParameter().getType().isAssignableFrom(SpendJson.class); + } + + @Override + public SpendJson resolveParameter(ParameterContext parameterContext, ExtensionContext extensionContext) throws ParameterResolutionException { + return extensionContext.getStore(CreateSpendingExtension.NAMESPACE).get(extensionContext.getUniqueId(), SpendJson.class); + } +} \ No newline at end of file diff --git a/niffler-e-2-e-tests/src/test/java/guru/qa/niffler/jupiter/extension/UserQueueExtension.java b/niffler-e-2-e-tests/src/test/java/guru/qa/niffler/jupiter/extension/UserQueueExtension.java new file mode 100644 index 000000000..ae11d93e6 --- /dev/null +++ b/niffler-e-2-e-tests/src/test/java/guru/qa/niffler/jupiter/extension/UserQueueExtension.java @@ -0,0 +1,92 @@ +package guru.qa.niffler.jupiter.extension; + +import io.qameta.allure.Allure; +import io.qameta.allure.AllureLifecycle; +import org.apache.commons.lang3.time.StopWatch; +import org.junit.jupiter.api.extension.*; +import org.junit.platform.commons.support.AnnotationSupport; + +import java.lang.annotation.*; +import java.util.Arrays; +import java.util.Date; +import java.util.Optional; +import java.util.Queue; +import java.util.concurrent.ConcurrentLinkedDeque; +import java.util.concurrent.TimeUnit; + +public class UserQueueExtension implements BeforeEachCallback, AfterEachCallback, ParameterResolver { + + public static final ExtensionContext.Namespace NAMESPACE = ExtensionContext.Namespace.create(UserQueueExtension.class); + + public record StaticUser(String username, String password, boolean empty){ + + } + + public static final Queue EMPTY_USER = new ConcurrentLinkedDeque<>(); + public static final Queue NOT_EMPTY_USER = new ConcurrentLinkedDeque<>(); + + static { + EMPTY_USER.add(new StaticUser("bee","1234",true)); + NOT_EMPTY_USER.add(new StaticUser("duck","12345",false)); + NOT_EMPTY_USER.add(new StaticUser("superduck","12345",false)); + } + + @Target(ElementType.PARAMETER) + @Retention(RetentionPolicy.RUNTIME) + public @interface UserType { + boolean empty() default true; + + } + + @Override + public void beforeEach(ExtensionContext context) throws Exception { + Arrays.stream(context.getRequiredTestMethod().getParameters()).filter(p -> AnnotationSupport.isAnnotated(p, UserType.class)) + .findFirst() + .map(p -> p.getAnnotation(UserType.class)) + .ifPresent( + ut -> { + Optional user = Optional.empty(); + StopWatch sw = StopWatch.createStarted(); + while (user.isEmpty() && sw.getTime(TimeUnit.SECONDS) < 30){ + user = ut.empty() + ? Optional.ofNullable(EMPTY_USER.poll()) + : Optional.ofNullable(NOT_EMPTY_USER.poll()); + } + Allure.getLifecycle().updateTestCase(testCase -> { + + testCase.setStart(new Date().getTime()); + }); + user.ifPresentOrElse( + u -> { + context.getStore(NAMESPACE) + .put( + context.getUniqueId(), + u + ); + + }, + () -> new IllegalStateException("Can't find user after 30 sec") + ); + } + + ); + } + + + @Override + public void afterEach(ExtensionContext context) throws Exception { + + } + + + @Override + public boolean supportsParameter(ParameterContext parameterContext, ExtensionContext extensionContext) throws ParameterResolutionException { + return parameterContext.getParameter().getType().isAssignableFrom(StaticUser.class) + && AnnotationSupport.isAnnotated(parameterContext.getParameter(),UserType.class); + } + + @Override + public Object resolveParameter(ParameterContext parameterContext, ExtensionContext extensionContext) throws ParameterResolutionException { + return extensionContext.getStore(NAMESPACE).get(extensionContext.getUniqueId(),StaticUser.class); + } +} diff --git a/niffler-e-2-e-tests/src/test/java/guru/qa/niffler/page/LoginPage.java b/niffler-e-2-e-tests/src/test/java/guru/qa/niffler/page/LoginPage.java index 034727184..ae8ed063a 100644 --- a/niffler-e-2-e-tests/src/test/java/guru/qa/niffler/page/LoginPage.java +++ b/niffler-e-2-e-tests/src/test/java/guru/qa/niffler/page/LoginPage.java @@ -2,12 +2,15 @@ import com.codeborne.selenide.SelenideElement; +import static com.codeborne.selenide.Condition.text; import static com.codeborne.selenide.Selenide.$; public class LoginPage { private final SelenideElement usernameInput = $("input[name='username']"); private final SelenideElement passwordInput = $("input[name='password']"); private final SelenideElement submitButton = $("button[type='submit']"); + private final SelenideElement createNewAccountButton = $("a.form__register"); + private final SelenideElement errorTitle = $(".form__error"); public MainPage login(String username, String password) { usernameInput.setValue(username); @@ -15,4 +18,14 @@ public MainPage login(String username, String password) { submitButton.click(); return new MainPage(); } + + public RegisterPage clickCreateAccountButton() { + createNewAccountButton.click(); + return new RegisterPage(); + } + + public LoginPage shouldErrorTitle(String value) { + errorTitle.shouldHave(text(value)); + return this; + } } diff --git a/niffler-e-2-e-tests/src/test/java/guru/qa/niffler/page/MainPage.java b/niffler-e-2-e-tests/src/test/java/guru/qa/niffler/page/MainPage.java index 49c86f202..1b4dcb79d 100644 --- a/niffler-e-2-e-tests/src/test/java/guru/qa/niffler/page/MainPage.java +++ b/niffler-e-2-e-tests/src/test/java/guru/qa/niffler/page/MainPage.java @@ -1,6 +1,7 @@ package guru.qa.niffler.page; import com.codeborne.selenide.ElementsCollection; +import com.codeborne.selenide.SelenideElement; import static com.codeborne.selenide.Condition.text; import static com.codeborne.selenide.Condition.visible; @@ -8,6 +9,11 @@ public class MainPage { private final ElementsCollection tableRows = $("#spendings tbody").$$("tr"); + private final SelenideElement statisticsHeader = $(".css-giaux5"); + private final SelenideElement historyHeader = $(".css-uxhuts"); + private final SelenideElement avatarImage = $("div.MuiAvatar-root"); + private final SelenideElement hrefProfile = $("a[href='/profile']"); + public EditSpendingPage editSpending(String spendingDescription) { tableRows.find(text(spendingDescription)).$$("td").get(5).click(); @@ -17,4 +23,27 @@ public EditSpendingPage editSpending(String spendingDescription) { public void checkThatTableContainsSpending(String spendingDescription) { tableRows.find(text(spendingDescription)).should(visible); } + + + public MainPage shouldStatisticsHeader(String value) { + statisticsHeader.shouldHave(text(value)); + return this; + } + + public MainPage shouldHistoryHeader(String value) { + historyHeader.shouldHave(text(value)); + return this; + } + + public MainPage clickAvatar(){ + avatarImage.click(); + return this; + } + + public ProfilePage clickProfile(){ + hrefProfile.click(); + return new ProfilePage(); + } + + } diff --git a/niffler-e-2-e-tests/src/test/java/guru/qa/niffler/page/ProfilePage.java b/niffler-e-2-e-tests/src/test/java/guru/qa/niffler/page/ProfilePage.java new file mode 100644 index 000000000..5917e3df8 --- /dev/null +++ b/niffler-e-2-e-tests/src/test/java/guru/qa/niffler/page/ProfilePage.java @@ -0,0 +1,110 @@ +package guru.qa.niffler.page; + +import com.codeborne.selenide.ElementsCollection; +import com.codeborne.selenide.Selenide; +import com.codeborne.selenide.SelenideElement; + +import static com.codeborne.selenide.Condition.text; +import static com.codeborne.selenide.Condition.visible; +import static com.codeborne.selenide.Selenide.*; + +public class ProfilePage { + + private final SelenideElement buttonUploadPicture = $("label.image__input-label"); + private final SelenideElement nameInput = $("input[name='name']"); + private final SelenideElement buttonSaveName = $("button[type='submit']"); + private final SelenideElement inputNewCategory = $("input[placeholder='Add new category']"); + private final SelenideElement successShowUpdateProfile = $("div.MuiAlert-message"); + private final SelenideElement archiveButtonSubmit = $x("//button[text()='Archive']"); + private final SelenideElement unarchiveButtonSubmit = $x("//button[text()='Unarchive']"); + private final ElementsCollection categoryList = $$(".MuiChip-root"); + private final SelenideElement successArchiveMessage = $x("//div[@class='MuiAlert-message css-1xsto0d']"); + private final SelenideElement successUnarchiveMessage = $x("//div[contains(@class,'MuiTypography-root MuiTypography-body1')]"); + private final SelenideElement showArchiveCategoryButton = $x("//input[@type='checkbox']"); + + + public ProfilePage editName(String name) { + nameInput.clear(); + nameInput.setValue(name); + return this; + } + + public ProfilePage shouldSuccessProfileUpdated(String message) { + successShowUpdateProfile.shouldHave(text(message)); + return this; + } + + public ProfilePage saveName(){ + buttonSaveName.click(); + return this; + } + + public ProfilePage clickArchiveButtonForCategoryName(String categoryName) { + // Проходим по списку категорий + for (int i = 0; i < categoryList.size(); i++) { + // Проверяем, содержит ли элемент текст категории + if (categoryList.get(i).text().equals(categoryName)) { + // Находим кнопку "Архивировать" внутри той же строки с названием категории + SelenideElement archiveButtonInRow = categoryList.get(i).parent().$(".MuiIconButton-sizeMedium[aria-label='Archive category']"); + // Кликаем по кнопке архивирования + archiveButtonInRow.click(); + break; + } + } + return this; + } + + public ProfilePage clickUnarchiveButtonForCategoryName(String categoryName) { + // Проходим по списку категорий + for (int i = 0; i < categoryList.size(); i++) { + // Проверяем, содержит ли элемент текст имени категории + if (categoryList.get(i).text().equals(categoryName)) { + // Находим кнопку "Разархивировать" внутри той же строки с названием категории + SelenideElement unarchiveButtonInRow = categoryList.get(i).parent().$("[data-testid='UnarchiveOutlinedIcon']"); + // Кликаем по кнопке разархивирования + unarchiveButtonInRow.click(); + break; + } + } + return this; + } + + public ProfilePage clickShowArchiveCategoryButton() { + Selenide.executeJavaScript("arguments[0].scrollIntoView(true);", showArchiveCategoryButton); + Selenide.executeJavaScript("arguments[0].click();", showArchiveCategoryButton); + return this; + } + + public ProfilePage clickArchiveButtonSubmit() { + archiveButtonSubmit.click(); + return this; + } + + public ProfilePage clickUnarchiveButtonSubmit() { + unarchiveButtonSubmit.click(); + return this; + } + + public ProfilePage shouldBeVisibleArchiveSuccessMessage(String value) { + successArchiveMessage.shouldHave(text("Category " + value + " is archived")).shouldBe(visible); + return this; + } + + public ProfilePage shouldBeVisibleUnarchiveSuccessMessage(String value) { + successUnarchiveMessage.shouldHave(text("Category " + value + " is unarchived")).shouldBe(visible); + return this; + } + + // Метод для проверки видимости активной категории + public ProfilePage shouldVisibleActiveCategory(String value) { + categoryList.findBy(text(value)).shouldBe(visible); + return this; + } + + // Метод для проверки, что архивная категория не видна + public ProfilePage shouldNotVisibleArchiveCategory(String value) { + categoryList.findBy(text(value)).shouldNotBe(visible); + return this; + } + +} diff --git a/niffler-e-2-e-tests/src/test/java/guru/qa/niffler/page/RegisterPage.java b/niffler-e-2-e-tests/src/test/java/guru/qa/niffler/page/RegisterPage.java new file mode 100644 index 000000000..5bca9e2df --- /dev/null +++ b/niffler-e-2-e-tests/src/test/java/guru/qa/niffler/page/RegisterPage.java @@ -0,0 +1,57 @@ +package guru.qa.niffler.page; + +import com.codeborne.selenide.SelenideElement; + +import static com.codeborne.selenide.Condition.text; +import static com.codeborne.selenide.Selenide.$; + +public class RegisterPage { + private final SelenideElement usernameInput = $("input[name='username']"); + private final SelenideElement passwordInput = $("input[name='password']"); + private final SelenideElement passwordSubmitInput = $("input[name='passwordSubmit']"); + private final SelenideElement successRegisterMessage = $(".form__paragraph_success"); + private final SelenideElement formError = $(".form__error"); + private final SelenideElement submitButton = $("button[type='submit']"); + private final SelenideElement signInButton = $(".form_sign-in"); + + public RegisterPage setUserName(String username){ + usernameInput.clear(); + usernameInput.setValue(username); + return this; + } + + public RegisterPage setPassword(String password){ + passwordInput.clear(); + passwordInput.setValue(password); + return this; + } + + public RegisterPage setPasswordSubmit(String password){ + passwordSubmitInput.clear(); + passwordSubmitInput.setValue(password); + return this; + } + + public RegisterPage clickSubmitButton() { + submitButton.click(); + return this; + } + + public LoginPage clickSignInButton() { + signInButton.click(); + return new LoginPage(); + } + + + public RegisterPage shouldSuccessRegister(String value) { + successRegisterMessage.shouldHave(text(value)); + return this; + } + + public RegisterPage shouldErrorRegister(String value) { + formError.shouldHave(text(value)); + return this; + } + + +} diff --git a/niffler-e-2-e-tests/src/test/java/guru/qa/niffler/test/web/LoginWebTest.java b/niffler-e-2-e-tests/src/test/java/guru/qa/niffler/test/web/LoginWebTest.java new file mode 100644 index 000000000..944765f14 --- /dev/null +++ b/niffler-e-2-e-tests/src/test/java/guru/qa/niffler/test/web/LoginWebTest.java @@ -0,0 +1,26 @@ +package guru.qa.niffler.test.web; + +import com.codeborne.selenide.Selenide; +import guru.qa.niffler.config.Config; +import guru.qa.niffler.jupiter.extension.BrowserExtension; +import guru.qa.niffler.page.LoginPage; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; + + +@ExtendWith(BrowserExtension.class) +public class LoginWebTest { + + private static final Config CFG = Config.getInstance(); + + + @Test + void userShouldStayOnLoginPageAfterLoginWithBadCredentials() { + final String errorTitle = "Bad credentials"; + + Selenide.open(CFG.frontUrl(), LoginPage.class) + .login("ola", "1234xx"); + + new LoginPage().shouldErrorTitle(errorTitle); + } +} diff --git a/niffler-e-2-e-tests/src/test/java/guru/qa/niffler/test/web/MainPageTest.java b/niffler-e-2-e-tests/src/test/java/guru/qa/niffler/test/web/MainPageTest.java new file mode 100644 index 000000000..440a8008c --- /dev/null +++ b/niffler-e-2-e-tests/src/test/java/guru/qa/niffler/test/web/MainPageTest.java @@ -0,0 +1,28 @@ +package guru.qa.niffler.test.web; + +import com.codeborne.selenide.Selenide; +import guru.qa.niffler.config.Config; +import guru.qa.niffler.jupiter.extension.BrowserExtension; +import guru.qa.niffler.page.LoginPage; +import guru.qa.niffler.page.MainPage; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; + +@ExtendWith(BrowserExtension.class) +public class MainPageTest { + + private static final Config CFG = Config.getInstance(); + + + @Test + void mainPageShouldBeDisplayedAfterSuccessLogin() { + final String statisticsHeader = "Statistics"; + final String historyHeader = "History of Spendings"; + + Selenide.open(CFG.frontUrl(), LoginPage.class) + .login("oleg", "123456"); + + new MainPage().shouldStatisticsHeader(statisticsHeader) + .shouldHistoryHeader(historyHeader); + } +} diff --git a/niffler-e-2-e-tests/src/test/java/guru/qa/niffler/test/web/ProfileTest.java b/niffler-e-2-e-tests/src/test/java/guru/qa/niffler/test/web/ProfileTest.java new file mode 100644 index 000000000..e7ed14a5d --- /dev/null +++ b/niffler-e-2-e-tests/src/test/java/guru/qa/niffler/test/web/ProfileTest.java @@ -0,0 +1,64 @@ +package guru.qa.niffler.test.web; + +import com.codeborne.selenide.Selenide; +import guru.qa.niffler.config.Config; +import guru.qa.niffler.jupiter.annotation.Category; +import guru.qa.niffler.jupiter.extension.BrowserExtension; +import guru.qa.niffler.model.CategoryJson; +import guru.qa.niffler.page.LoginPage; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; + +@ExtendWith(BrowserExtension.class) +public class ProfileTest { + + private static final Config CFG = Config.getInstance(); + final String messageUpdateProfile = "Profile successfully updated"; + + + @Test + void pictureShouldBeUploaded() { + Selenide.open(CFG.frontUrl(), LoginPage.class) + .login("oleg", "123456") + .clickAvatar() + .clickProfile() + .editName("Petya") + .saveName() + .shouldSuccessProfileUpdated(messageUpdateProfile); + } + + @Category( + username = "superduck1", + isArchive = false + ) + @Test + void archivedCategoryShouldNotPresentInCategoriesList(CategoryJson category) throws InterruptedException { + Selenide.open(CFG.frontUrl(), LoginPage.class) + .login("superduck1", "12345") + .clickAvatar() + .clickProfile() + .clickArchiveButtonForCategoryName(category.name()) + .clickArchiveButtonSubmit() + .shouldBeVisibleArchiveSuccessMessage(category.name()) + .shouldNotVisibleArchiveCategory(category.name()); + } + + // + @Category( + username = "superduck1", + isArchive = true + ) + @Test + void activeCategoryShouldPresentInCategoriesList(CategoryJson category) throws InterruptedException { + Selenide.open(CFG.frontUrl(), LoginPage.class) + .login("superduck1", "12345") + .clickAvatar() + .clickProfile() + .clickShowArchiveCategoryButton() + .clickUnarchiveButtonForCategoryName(category.name()) + .clickUnarchiveButtonSubmit() + .shouldBeVisibleUnarchiveSuccessMessage(category.name()) + .clickShowArchiveCategoryButton() + .shouldVisibleActiveCategory(category.name()); + } +} diff --git a/niffler-e-2-e-tests/src/test/java/guru/qa/niffler/test/web/RegisterWebTest.java b/niffler-e-2-e-tests/src/test/java/guru/qa/niffler/test/web/RegisterWebTest.java new file mode 100644 index 000000000..de542e544 --- /dev/null +++ b/niffler-e-2-e-tests/src/test/java/guru/qa/niffler/test/web/RegisterWebTest.java @@ -0,0 +1,71 @@ +package guru.qa.niffler.test.web; + +import com.codeborne.selenide.Selenide; +import com.github.javafaker.Faker; +import guru.qa.niffler.config.Config; +import guru.qa.niffler.jupiter.extension.BrowserExtension; +import guru.qa.niffler.page.LoginPage; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; + + +@ExtendWith(BrowserExtension.class) +public class RegisterWebTest { + + private static final Config CFG = Config.getInstance(); + Faker faker = new Faker(); + String pass = faker.internet().password(3, 9); + + @Test + void shouldRegisterNewUser() { + final String messageSuccessRegister = "Congratulations! You've registered!"; + + Selenide.open(CFG.frontUrl(), LoginPage.class) + .clickCreateAccountButton() + .setUserName(faker.name().username()) + .setPassword(pass) + .setPasswordSubmit(pass) + .clickSubmitButton() + .shouldSuccessRegister(messageSuccessRegister); + } + + + + @Test + void shouldNotRegisterUserWithExistingUsername() { + String name = faker.name().username(); + final String messageSuccessRegister = "Congratulations! You've registered!"; + final String messageErrorExistRegister = "Username `" + name + "` already exists"; + + Selenide.open(CFG.frontUrl(), LoginPage.class) + .clickCreateAccountButton() + .setUserName(name) + .setPassword(pass) + .setPasswordSubmit(pass) + .clickSubmitButton() + .shouldSuccessRegister(messageSuccessRegister) + .clickSignInButton() + .clickCreateAccountButton() + .setUserName(name) + .setPassword(pass) + .setPasswordSubmit(pass) + .clickSubmitButton() + .shouldErrorRegister(messageErrorExistRegister); + } + + + + @Test + void shouldShowErrorIfPasswordAndConfirmPasswordAreNotEqual() { + String name = faker.name().username(); + final String messageErrorNotEqualPass = "Passwords should be equal"; + + Selenide.open(CFG.frontUrl(), LoginPage.class) + .clickCreateAccountButton() + .setUserName(name) + .setPassword(faker.internet().password(3, 15)) + .setPasswordSubmit(pass) + .clickSubmitButton() + .shouldErrorRegister(messageErrorNotEqualPass); + } +} diff --git a/niffler-e-2-e-tests/src/test/java/guru/qa/niffler/test/web/SpendingWebTest.java b/niffler-e-2-e-tests/src/test/java/guru/qa/niffler/test/web/SpendingWebTest.java index 3f503cd8e..1af875862 100644 --- a/niffler-e-2-e-tests/src/test/java/guru/qa/niffler/test/web/SpendingWebTest.java +++ b/niffler-e-2-e-tests/src/test/java/guru/qa/niffler/test/web/SpendingWebTest.java @@ -2,6 +2,7 @@ import com.codeborne.selenide.Selenide; import guru.qa.niffler.config.Config; +import guru.qa.niffler.jupiter.annotation.DisabledByIssue; import guru.qa.niffler.jupiter.extension.BrowserExtension; import guru.qa.niffler.jupiter.annotation.Spending; import guru.qa.niffler.model.SpendJson; @@ -17,12 +18,14 @@ public class SpendingWebTest { @Spending( username = "duck", - category = "Обучение", + category = "Супер Обучение", description = "Обучение Advanced 2.0", amount = 79990 ) + @DisabledByIssue("1") @Test void categoryDescriptionShouldBeChangedFromTable(SpendJson spend) { + final String newDescription = "Обучение Niffler Next Generation"; Selenide.open(CFG.frontUrl(), LoginPage.class) @@ -34,4 +37,3 @@ void categoryDescriptionShouldBeChangedFromTable(SpendJson spend) { new MainPage().checkThatTableContainsSpending(newDescription); } } - diff --git a/niffler-e-2-e-tests/src/test/java/guru/qa/niffler/test/web/UserQueueTest.java b/niffler-e-2-e-tests/src/test/java/guru/qa/niffler/test/web/UserQueueTest.java new file mode 100644 index 000000000..d2f3fafb2 --- /dev/null +++ b/niffler-e-2-e-tests/src/test/java/guru/qa/niffler/test/web/UserQueueTest.java @@ -0,0 +1,29 @@ +package guru.qa.niffler.test.web; + + +import ch.qos.logback.core.net.SyslogOutputStream; +import com.codeborne.selenide.Selenide; +import guru.qa.niffler.jupiter.extension.UserQueueExtension; +import guru.qa.niffler.jupiter.extension.UserQueueExtension.*; + +import guru.qa.niffler.model.CategoryJson; +import guru.qa.niffler.page.LoginPage; +import io.opentelemetry.exporter.logging.SystemOutLogRecordExporter; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; + +@ExtendWith(UserQueueExtension.class) +public class UserQueueTest { + + @Test + void testWithEmptyUser(@UserType(empty = true) StaticUser user) throws InterruptedException { + Thread.sleep(1000); + System.out.println(user); + } + + @Test + void testWithNotEmptyUser(@UserType(empty = false) StaticUser user) throws InterruptedException { + Thread.sleep(1000); + System.out.println(user); + } +} diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 000000000..2768c0aee --- /dev/null +++ b/package-lock.json @@ -0,0 +1,6 @@ +{ + "name": "niffler-ng-6", + "lockfileVersion": 3, + "requires": true, + "packages": {} +}