From 46f3de4d55d94a1249e69bec5a43f07da3ab2a01 Mon Sep 17 00:00:00 2001 From: spawn Date: Sat, 18 Apr 2026 03:27:16 +0300 Subject: [PATCH] Simplify mobile test architecture and stabilize data setup --- .env.example | 9 +- .env.production.example | 4 + README.md | 36 ++----- .../ru/otus/mobile/config/MobileConfig.java | 33 ++---- .../ru/otus/mobile/config/TestAccount.java | 97 ++++------------- .../ru/otus/mobile/config/TestEmulator.java | 25 +++++ src/main/java/ru/otus/mobile/db/DbClient.java | 69 +++++++----- .../ru/otus/mobile/driver/MobileSession.java | 46 -------- .../mobile/driver/MobileSessionFactory.java | 37 ------- .../mobile/extensions/MobileExtension.java | 94 +++++++++++----- .../java/ru/otus/mobile/guice/CoreModule.java | 16 ++- .../ru/otus/mobile/guice/SessionModule.java | 30 ------ .../java/ru/otus/mobile/page/UsersPage.java | 102 +++++++++++++++--- .../ru/otus/mobile/page/WishlistsPage.java | 11 ++ .../java/ru/otus/mobile/tests/GiftsTest.java | 18 +++- .../ru/otus/mobile/tests/ReservationTest.java | 23 ++-- .../ru/otus/mobile/tests/WishlistsTest.java | 14 ++- 17 files changed, 326 insertions(+), 338 deletions(-) create mode 100644 .env.production.example create mode 100644 src/main/java/ru/otus/mobile/config/TestEmulator.java delete mode 100644 src/main/java/ru/otus/mobile/driver/MobileSession.java delete mode 100644 src/main/java/ru/otus/mobile/driver/MobileSessionFactory.java delete mode 100644 src/main/java/ru/otus/mobile/guice/SessionModule.java diff --git a/.env.example b/.env.example index e9b2765..92d3a50 100644 --- a/.env.example +++ b/.env.example @@ -1,11 +1,4 @@ DB_URL=jdbc:postgresql://:/ DB_USER= DB_PASSWORD= - -WISHLISTS_USERNAME= -WISHLISTS_PASSWORD= -GIFTS_USERNAME= -GIFTS_PASSWORD= -RESERVATION_USERNAME= -RESERVATION_PASSWORD= -RESERVATION_OWNER= +MOBILE_HOST=127.0.0.1 diff --git a/.env.production.example b/.env.production.example new file mode 100644 index 0000000..42dde3c --- /dev/null +++ b/.env.production.example @@ -0,0 +1,4 @@ +DB_URL=jdbc:postgresql://sql.otus.kartushin.su:5432/wishlist +DB_USER= +DB_PASSWORD= +MOBILE_HOST=127.0.0.1 diff --git a/README.md b/README.md index e85d501..e958e58 100644 --- a/README.md +++ b/README.md @@ -59,13 +59,7 @@ PowerShell: $env:DB_URL="jdbc:postgresql://:/" $env:DB_USER="" $env:DB_PASSWORD="" -$env:WISHLISTS_USERNAME="" -$env:WISHLISTS_PASSWORD="" -$env:GIFTS_USERNAME="" -$env:GIFTS_PASSWORD="" -$env:RESERVATION_USERNAME="" -$env:RESERVATION_PASSWORD="" -$env:RESERVATION_OWNER="" +$env:MOBILE_HOST="127.0.0.1" ``` bash: @@ -73,30 +67,22 @@ bash: export DB_URL="jdbc:postgresql://:/" export DB_USER="" export DB_PASSWORD="" -export WISHLISTS_USERNAME="" -export WISHLISTS_PASSWORD="" -export GIFTS_USERNAME="" -export GIFTS_PASSWORD="" -export RESERVATION_USERNAME="" -export RESERVATION_PASSWORD="" -export RESERVATION_OWNER="" +export MOBILE_HOST="127.0.0.1" ``` Оба варианта эквивалентны: тесты используют переменные окружения процесса. +Тестовые пользователи зафиксированы в коде (`TestAccount`): +- `user1us` — тест списков желаний; +- `user2us` — тест подарков; +- `user3us` — пользователь, который резервирует подарок; +- `user4us` — пользователь-владелец списка в тесте резервирования. + +Для production-подобного запуска используйте шаблон `.env.production.example` и не коммитьте реальные значения в репозиторий. + Тесты запускаются параллельно по классам (2 потока) и распределяются по эмуляторам через очередь. 4. Запустить тесты: ```bash mvn test ``` -Примечание: если нужен запуск только на одном эмуляторе, можно поднять только `wiremock` и `android-emulator-1`, а перед `mvn test` задать: - -PowerShell: -```powershell -$env:MOBILE_EMULATORS="android-emulator-1|http://127.0.0.1:4723|Android Emulator" -``` -bash: -```bash -export MOBILE_EMULATORS="android-emulator-1|http://127.0.0.1:4723|Android Emulator" -``` - +Примечание: эмуляторы зафиксированы в enum `TestEmulator` (порты `4723` и `4725`), в конфигурации задается только хост (`MOBILE_HOST`). diff --git a/src/main/java/ru/otus/mobile/config/MobileConfig.java b/src/main/java/ru/otus/mobile/config/MobileConfig.java index 28b3fda..6cd80a2 100644 --- a/src/main/java/ru/otus/mobile/config/MobileConfig.java +++ b/src/main/java/ru/otus/mobile/config/MobileConfig.java @@ -1,23 +1,22 @@ package ru.otus.mobile.config; -import java.util.ArrayList; import java.util.List; public final class MobileConfig { private final String appPackage; private final String appUrl; - private final String reservationOwnerUsername; + private final String appiumHost; private final List emulators; public MobileConfig( String appPackage, String appUrl, - String reservationOwnerUsername, + String appiumHost, List emulators ) { this.appPackage = appPackage; this.appUrl = appUrl; - this.reservationOwnerUsername = reservationOwnerUsername; + this.appiumHost = appiumHost; this.emulators = List.copyOf(emulators); } @@ -29,33 +28,13 @@ public final class MobileConfig { return appUrl; } - public String reservationOwnerUsername() { - return reservationOwnerUsername; + public String appiumHost() { + return appiumHost; } public List emulators() { return emulators; } - public record Emulator(String id, String appiumUrl, String deviceName, String appUrl) { - public static List parse(String rawValue, String defaultAppUrl) { - List emulators = new ArrayList<>(); - for (String entry : rawValue.split(",")) { - String trimmed = entry.trim(); - if (trimmed.isEmpty()) { - continue; - } - String[] parts = trimmed.split("\\|"); - String id = parts.length > 0 ? parts[0].trim() : "emulator-1"; - String appiumUrl = parts.length > 1 ? parts[1].trim() : "http://127.0.0.1:4723"; - String deviceName = parts.length > 2 ? parts[2].trim() : "Android Emulator"; - String emulatorAppUrl = parts.length > 3 ? parts[3].trim() : defaultAppUrl; - emulators.add(new Emulator(id, appiumUrl, deviceName, emulatorAppUrl)); - } - if (emulators.isEmpty()) { - emulators.add(new Emulator("emulator-1", "http://127.0.0.1:4723", "Android Emulator", defaultAppUrl)); - } - return emulators; - } - } + public record Emulator(String id, String appiumUrl, String deviceName, String appUrl) { } } diff --git a/src/main/java/ru/otus/mobile/config/TestAccount.java b/src/main/java/ru/otus/mobile/config/TestAccount.java index a5ad00d..6988e42 100644 --- a/src/main/java/ru/otus/mobile/config/TestAccount.java +++ b/src/main/java/ru/otus/mobile/config/TestAccount.java @@ -1,93 +1,34 @@ package ru.otus.mobile.config; +import ru.otus.mobile.db.DbClient; + +import java.util.function.BiConsumer; + public enum TestAccount { - WISHLISTS("WISHLISTS", "user1us", "user1us", """ - WITH target_user AS ( - SELECT id FROM users WHERE username = ? - ), - delete_gifts AS ( - DELETE FROM gifts - WHERE wish_id IN (SELECT id FROM wishlists WHERE user_id IN (SELECT id FROM target_user)) - ), - delete_wishlists AS ( - DELETE FROM wishlists WHERE user_id IN (SELECT id FROM target_user) - ) - SELECT 1; - """), - GIFTS("GIFTS", "user2us", "user2us", """ - WITH target_user AS ( - SELECT id FROM users WHERE username = ? - ), - delete_gifts AS ( - DELETE FROM gifts - WHERE wish_id IN (SELECT id FROM wishlists WHERE user_id IN (SELECT id FROM target_user)) - ), - delete_wishlists AS ( - DELETE FROM wishlists WHERE user_id IN (SELECT id FROM target_user) - ) - SELECT 1; - """), - RESERVATION("RESERVATION", "user3us", "user3us", """ - WITH owner_user AS ( - SELECT id FROM users WHERE username = ? - ), - target_user AS ( - SELECT id FROM users WHERE username = ? - ), - delete_owner_gifts AS ( - DELETE FROM gifts - WHERE wish_id IN (SELECT id FROM wishlists WHERE user_id IN (SELECT id FROM owner_user)) - ), - delete_owner_wishlists AS ( - DELETE FROM wishlists WHERE user_id IN (SELECT id FROM owner_user) - ), - delete_target_gifts AS ( - DELETE FROM gifts - WHERE wish_id IN (SELECT id FROM wishlists WHERE user_id IN (SELECT id FROM target_user)) - ), - delete_target_wishlists AS ( - DELETE FROM wishlists WHERE user_id IN (SELECT id FROM target_user) - ), - owner_wishlist AS ( - INSERT INTO wishlists (id, title, description, user_id) - SELECT gen_random_uuid(), 'Owner Wishlist', 'Prepared for reservation test', id - FROM owner_user - RETURNING id - ) - INSERT INTO gifts (id, name, description, price, is_reserved, store_url, image_url, wish_id) - SELECT gen_random_uuid(), 'Owner Gift', 'Prepared for reservation test', 100, false, NULL, NULL, id - FROM owner_wishlist; - """); + WISHLISTS("user1us", "user1us", DbClient::clearWishlistsByUsername), + GIFTS("user2us", "user2us", DbClient::clearGiftsByUsername), + RESERVATION("user3us", "user3us", DbClient::doNothing), + RESERVATION_OWNER("user4us", "user4us", DbClient::clearReservationByUsername); - private final String envPrefix; - private final String defaultUsername; - private final String defaultPassword; - private final String resetSql; + private final String username; + private final String password; + private final BiConsumer dataPreparation; - TestAccount(String envPrefix, String defaultUsername, String defaultPassword, String resetSql) { - this.envPrefix = envPrefix; - this.defaultUsername = defaultUsername; - this.defaultPassword = defaultPassword; - this.resetSql = resetSql; + TestAccount(String username, String password, BiConsumer dataPreparation) { + this.username = username; + this.password = password; + this.dataPreparation = dataPreparation; } public String username() { - return resolve(envPrefix + "_USERNAME", defaultUsername); + return username; } public String password() { - return resolve(envPrefix + "_PASSWORD", defaultPassword); + return password; } - public String resetSql() { - return resetSql; - } - - private String resolve(String envKey, String defaultValue) { - String value = System.getenv(envKey); - if (value == null || value.isBlank()) { - return defaultValue; - } - return value; + public BiConsumer dataPreparation() { + return dataPreparation; } } diff --git a/src/main/java/ru/otus/mobile/config/TestEmulator.java b/src/main/java/ru/otus/mobile/config/TestEmulator.java new file mode 100644 index 0000000..f7ad2cf --- /dev/null +++ b/src/main/java/ru/otus/mobile/config/TestEmulator.java @@ -0,0 +1,25 @@ +package ru.otus.mobile.config; + +public enum TestEmulator { + EMULATOR_1("android-emulator-1", 4723, "Android Emulator"), + EMULATOR_2("android-emulator-2", 4725, "Android Emulator"); + + private final String id; + private final int appiumPort; + private final String deviceName; + + TestEmulator(String id, int appiumPort, String deviceName) { + this.id = id; + this.appiumPort = appiumPort; + this.deviceName = deviceName; + } + + public MobileConfig.Emulator toEmulator(String appiumHost, String appUrl) { + return new MobileConfig.Emulator( + id, + "http://" + appiumHost + ":" + appiumPort, + deviceName, + appUrl + ); + } +} diff --git a/src/main/java/ru/otus/mobile/db/DbClient.java b/src/main/java/ru/otus/mobile/db/DbClient.java index 0d68828..ac7ce14 100644 --- a/src/main/java/ru/otus/mobile/db/DbClient.java +++ b/src/main/java/ru/otus/mobile/db/DbClient.java @@ -1,8 +1,6 @@ package ru.otus.mobile.db; import com.google.inject.Inject; -import ru.otus.mobile.config.MobileConfig; -import ru.otus.mobile.config.TestAccount; import java.sql.Connection; import java.sql.DriverManager; @@ -11,37 +9,56 @@ import java.sql.SQLException; public final class DbClient { private final DbConfig config; - private final MobileConfig mobileConfig; @Inject - public DbClient(DbConfig config, MobileConfig mobileConfig) { + public DbClient(DbConfig config) { this.config = config; - this.mobileConfig = mobileConfig; } - public void resetData(TestAccount account) { - try (Connection connection = DriverManager.getConnection(config.url(), config.user(), config.password()); - PreparedStatement statement = connection.prepareStatement(account.resetSql())) { - if (account == TestAccount.RESERVATION) { - statement.setString(1, mobileConfig.reservationOwnerUsername()); - statement.setString(2, account.username()); - } else { - statement.setString(1, account.username()); + public void clearWishlistsByUsername(String username) { + executeUpdate(""" + DELETE FROM wishlists + WHERE user_id IN (SELECT id FROM users WHERE username = ?) + """, username, "clear wishlists"); + } + + public void clearGiftsByUsername(String username) { + executeUpdate(""" + DELETE FROM gifts + WHERE wish_id IN ( + SELECT w.id + FROM wishlists w + JOIN users u ON u.id = w.user_id + WHERE u.username = ? + ) + """, username, "clear gifts"); + } + + public void clearReservationByUsername(String username) { + executeUpdate(""" + UPDATE gifts + SET is_reserved = false + WHERE wish_id IN ( + SELECT w.id + FROM wishlists w + JOIN users u ON u.id = w.user_id + WHERE u.username = ? + ) + """, username, "reset reservation status"); + } + + public void doNothing(String username) { + // no-op preparation for accounts that do not require DB setup + } + + private void executeUpdate(String sql, String username, String operation) { + try (Connection connection = DriverManager.getConnection(config.url(), config.user(), config.password())) { + try (PreparedStatement statement = connection.prepareStatement(sql)) { + statement.setString(1, username); + statement.executeUpdate(); } - statement.execute(); } catch (SQLException e) { - throw new IllegalStateException("Failed to prepare test data for account " + account.name(), e); - } - } - - public void prepareReservationData(String ownerUsername, String reservationUsername) { - try (Connection connection = DriverManager.getConnection(config.url(), config.user(), config.password()); - PreparedStatement statement = connection.prepareStatement(TestAccount.RESERVATION.resetSql())) { - statement.setString(1, ownerUsername); - statement.setString(2, reservationUsername); - statement.execute(); - } catch (SQLException e) { - throw new IllegalStateException("Failed to prepare reservation data for owner " + ownerUsername, e); + throw new IllegalStateException("Failed to " + operation + " for username " + username, e); } } } diff --git a/src/main/java/ru/otus/mobile/driver/MobileSession.java b/src/main/java/ru/otus/mobile/driver/MobileSession.java deleted file mode 100644 index bebdb5c..0000000 --- a/src/main/java/ru/otus/mobile/driver/MobileSession.java +++ /dev/null @@ -1,46 +0,0 @@ -package ru.otus.mobile.driver; - -import com.codeborne.selenide.WebDriverRunner; -import com.google.inject.Injector; -import io.appium.java_client.android.AndroidDriver; -import ru.otus.mobile.config.TestContext; - -public final class MobileSession { - private final TestContext context; - private final Injector injector; - private final EmulatorQueue emulatorQueue; - private final LogcatCollector logcatCollector; - - public MobileSession( - TestContext context, - Injector injector, - EmulatorQueue emulatorQueue, - LogcatCollector logcatCollector - ) { - this.context = context; - this.injector = injector; - this.emulatorQueue = emulatorQueue; - this.logcatCollector = logcatCollector; - } - - public void activate() { - WebDriverRunner.setWebDriver(context.driver()); - } - - public void injectMembers(Object target) { - injector.injectMembers(target); - } - - public void close() { - logcatCollector.save(context); - AndroidDriver driver = context.driver(); - try { - if (driver != null) { - driver.quit(); - } - } finally { - WebDriverRunner.closeWebDriver(); - emulatorQueue.release(context.emulator()); - } - } -} diff --git a/src/main/java/ru/otus/mobile/driver/MobileSessionFactory.java b/src/main/java/ru/otus/mobile/driver/MobileSessionFactory.java deleted file mode 100644 index cea14be..0000000 --- a/src/main/java/ru/otus/mobile/driver/MobileSessionFactory.java +++ /dev/null @@ -1,37 +0,0 @@ -package ru.otus.mobile.driver; - -import com.google.inject.Inject; -import com.google.inject.Injector; -import io.appium.java_client.android.AndroidDriver; -import ru.otus.mobile.config.MobileConfig; -import ru.otus.mobile.config.TestAccount; -import ru.otus.mobile.config.TestContext; -import ru.otus.mobile.guice.SessionModule; - -public final class MobileSessionFactory { - private final Injector rootInjector; - private final EmulatorQueue emulatorQueue; - private final MobileDriverFactory driverFactory; - private final LogcatCollector logcatCollector; - - @Inject - public MobileSessionFactory( - Injector rootInjector, - EmulatorQueue emulatorQueue, - MobileDriverFactory driverFactory, - LogcatCollector logcatCollector - ) { - this.rootInjector = rootInjector; - this.emulatorQueue = emulatorQueue; - this.driverFactory = driverFactory; - this.logcatCollector = logcatCollector; - } - - public MobileSession create(TestAccount account, String testName) { - MobileConfig.Emulator emulator = emulatorQueue.acquire(); - AndroidDriver driver = driverFactory.create(emulator); - TestContext context = new TestContext(account, emulator, driver, testName); - Injector childInjector = rootInjector.createChildInjector(new SessionModule(context)); - return new MobileSession(context, childInjector, emulatorQueue, logcatCollector); - } -} diff --git a/src/main/java/ru/otus/mobile/extensions/MobileExtension.java b/src/main/java/ru/otus/mobile/extensions/MobileExtension.java index 75879ae..841e123 100644 --- a/src/main/java/ru/otus/mobile/extensions/MobileExtension.java +++ b/src/main/java/ru/otus/mobile/extensions/MobileExtension.java @@ -1,53 +1,71 @@ package ru.otus.mobile.extensions; +import com.codeborne.selenide.WebDriverRunner; import com.google.inject.Guice; import com.google.inject.Injector; +import io.appium.java_client.android.AndroidDriver; import org.junit.jupiter.api.extension.AfterEachCallback; -import org.junit.jupiter.api.extension.BeforeEachCallback; +import org.junit.jupiter.api.extension.AfterTestExecutionCallback; 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.jupiter.api.extension.TestInstancePostProcessor; import ru.otus.mobile.annotations.MobileUser; +import ru.otus.mobile.config.MobileConfig; import ru.otus.mobile.config.TestAccount; -import ru.otus.mobile.db.DbClient; -import ru.otus.mobile.driver.MobileSession; -import ru.otus.mobile.driver.MobileSessionFactory; +import ru.otus.mobile.config.TestContext; +import ru.otus.mobile.driver.EmulatorQueue; +import ru.otus.mobile.driver.LogcatCollector; +import ru.otus.mobile.driver.MobileDriverFactory; import ru.otus.mobile.guice.CoreModule; -import ru.otus.mobile.page.LoginPage; public final class MobileExtension implements - BeforeEachCallback, + TestInstancePostProcessor, + ParameterResolver, + AfterTestExecutionCallback, AfterEachCallback { private static final ExtensionContext.Namespace NAMESPACE = ExtensionContext.Namespace.create(MobileExtension.class); private static final String SESSION_KEY = "mobile.session"; + private static final Injector INJECTOR = Guice.createInjector(new CoreModule()); @Override - public void beforeEach(ExtensionContext context) { - Injector injector = rootInjector(context); - TestAccount account = resolveAccount(context); - injector.getInstance(DbClient.class).resetData(account); + public void postProcessTestInstance(Object testInstance, ExtensionContext context) { + INJECTOR.injectMembers(testInstance); + } - MobileSessionFactory sessionFactory = injector.getInstance(MobileSessionFactory.class); - MobileSession session = sessionFactory.create(account, context.getDisplayName()); - session.activate(); - session.injectMembers(context.getRequiredTestInstance()); - context.getStore(NAMESPACE).put(SESSION_KEY, session); - injector.getInstance(LoginPage.class).login(account); + @Override + public boolean supportsParameter(ParameterContext parameterContext, ExtensionContext extensionContext) + throws ParameterResolutionException { + return TestContext.class.equals(parameterContext.getParameter().getType()); + } + + @Override + public Object resolveParameter(ParameterContext parameterContext, ExtensionContext context) + throws ParameterResolutionException { + TestAccount account = resolveAccount(context); + TestContext testContext = createContext(account, context.getDisplayName()); + context.getStore(NAMESPACE).put(SESSION_KEY, testContext); + return testContext; + } + + @Override + public void afterTestExecution(ExtensionContext context) { + TestContext testContext = context.getStore(NAMESPACE).get(SESSION_KEY, TestContext.class); + if (testContext != null) { + INJECTOR.getInstance(LogcatCollector.class).save(testContext); + } } @Override public void afterEach(ExtensionContext context) { - MobileSession session = context.getStore(NAMESPACE).remove(SESSION_KEY, MobileSession.class); - if (session != null) { - session.close(); + TestContext testContext = context.getStore(NAMESPACE).remove(SESSION_KEY, TestContext.class); + if (testContext != null) { + closeContext(testContext); } } - private Injector rootInjector(ExtensionContext context) { - return context.getRoot() - .getStore(NAMESPACE) - .getOrComputeIfAbsent("root.injector", key -> Guice.createInjector(new CoreModule()), Injector.class); - } - private TestAccount resolveAccount(ExtensionContext context) { MobileUser annotation = context.getRequiredTestClass().getAnnotation(MobileUser.class); if (annotation == null) { @@ -55,4 +73,30 @@ public final class MobileExtension implements } return annotation.value(); } + + private TestContext createContext(TestAccount account, String testName) { + EmulatorQueue queue = INJECTOR.getInstance(EmulatorQueue.class); + MobileConfig.Emulator emulator = queue.acquire(); + try { + AndroidDriver driver = INJECTOR.getInstance(MobileDriverFactory.class).create(emulator); + WebDriverRunner.setWebDriver(driver); + return new TestContext(account, emulator, driver, testName); + } catch (RuntimeException e) { + queue.release(emulator); + throw e; + } + } + + private void closeContext(TestContext context) { + AndroidDriver driver = context.driver(); + EmulatorQueue queue = INJECTOR.getInstance(EmulatorQueue.class); + try { + if (driver != null) { + driver.quit(); + } + } finally { + WebDriverRunner.closeWebDriver(); + queue.release(context.emulator()); + } + } } diff --git a/src/main/java/ru/otus/mobile/guice/CoreModule.java b/src/main/java/ru/otus/mobile/guice/CoreModule.java index 233c422..474bb15 100644 --- a/src/main/java/ru/otus/mobile/guice/CoreModule.java +++ b/src/main/java/ru/otus/mobile/guice/CoreModule.java @@ -5,11 +5,13 @@ import com.google.inject.Provides; import com.google.inject.Singleton; import ru.otus.mobile.config.MobileConfig; import ru.otus.mobile.config.ProjectPaths; +import ru.otus.mobile.config.TestEmulator; import ru.otus.mobile.db.DbConfig; import ru.otus.mobile.driver.EmulatorQueue; import ru.otus.mobile.driver.LogcatCollector; import ru.otus.mobile.driver.MobileDriverFactory; +import java.util.Arrays; import java.util.List; public final class CoreModule extends AbstractModule { @@ -22,15 +24,11 @@ public final class CoreModule extends AbstractModule { MobileConfig mobileConfig() { String appPackage = value("app.package", "APP_PACKAGE", "ru.otus.wishlist"); String appUrl = value("app.url", "APP_URL", "http://wiremock:8080/wishlist.apk"); - String reservationOwner = value("reservation.owner", "RESERVATION_OWNER", "user4us"); - String rawEmulators = value( - "mobile.emulators", - "MOBILE_EMULATORS", - "android-emulator-1|http://127.0.0.1:4723|Android Emulator," - + "android-emulator-2|http://127.0.0.1:4725|Android Emulator" - ); - List emulators = MobileConfig.Emulator.parse(rawEmulators, appUrl); - return new MobileConfig(appPackage, appUrl, reservationOwner, emulators); + String appiumHost = value("mobile.host", "MOBILE_HOST", "127.0.0.1"); + List emulators = Arrays.stream(TestEmulator.values()) + .map(emulator -> emulator.toEmulator(appiumHost, appUrl)) + .toList(); + return new MobileConfig(appPackage, appUrl, appiumHost, emulators); } @Provides diff --git a/src/main/java/ru/otus/mobile/guice/SessionModule.java b/src/main/java/ru/otus/mobile/guice/SessionModule.java deleted file mode 100644 index 7174f5c..0000000 --- a/src/main/java/ru/otus/mobile/guice/SessionModule.java +++ /dev/null @@ -1,30 +0,0 @@ -package ru.otus.mobile.guice; - -import com.google.inject.AbstractModule; -import com.google.inject.Provides; -import io.appium.java_client.android.AndroidDriver; -import ru.otus.mobile.config.TestAccount; -import ru.otus.mobile.config.TestContext; - -public final class SessionModule extends AbstractModule { - private final TestContext testContext; - - public SessionModule(TestContext testContext) { - this.testContext = testContext; - } - - @Override - protected void configure() { - bind(TestContext.class).toInstance(testContext); - } - - @Provides - TestAccount testAccount() { - return testContext.account(); - } - - @Provides - AndroidDriver androidDriver() { - return testContext.driver(); - } -} diff --git a/src/main/java/ru/otus/mobile/page/UsersPage.java b/src/main/java/ru/otus/mobile/page/UsersPage.java index 846e5f9..6fa6ce4 100644 --- a/src/main/java/ru/otus/mobile/page/UsersPage.java +++ b/src/main/java/ru/otus/mobile/page/UsersPage.java @@ -20,6 +20,15 @@ public final class UsersPage extends AbsBasePage { byId("users_content").shouldBe(Condition.visible); } + public void filterByUsername(String username) { + openFilter(); + SelenideElement input = filterInput().shouldBe(Condition.visible, Duration.ofSeconds(15)); + input.clear(); + input.setValue(username); + applyFilter(); + byTextContains(username).shouldBe(Condition.visible, Duration.ofSeconds(15)); + } + public void openUser(String username) { String appPackage = config().appPackage(); String xpath = "//android.widget.TextView[@resource-id='" + appPackage + ":id/username'" @@ -33,18 +42,6 @@ public final class UsersPage extends AbsBasePage { byTextContains(username).shouldBe(Condition.visible, Duration.ofSeconds(15)); } - public String firstVisibleUsernameExcluding(String excludedUsername) { - var usernames = allById("username") - .shouldHave(CollectionCondition.sizeGreaterThan(0), Duration.ofSeconds(15)); - for (SelenideElement usernameElement : usernames) { - String username = usernameElement.getText().trim(); - if (!username.isEmpty() && !username.equals(excludedUsername)) { - return username; - } - } - throw new IllegalStateException("No visible username found excluding '" + excludedUsername + "'."); - } - public void openFirstWishlist() { allById("wishlist_item") .shouldHave(CollectionCondition.sizeGreaterThan(0), Duration.ofSeconds(15)) @@ -76,4 +73,85 @@ public final class UsersPage extends AbsBasePage { public void toggleReservation() { tap("reserved"); } + + private void openFilter() { + if (byId("users_filter").exists()) { + tap("users_filter"); + byId("users_filter_bottom_sheet").shouldBe(Condition.visible, Duration.ofSeconds(15)); + return; + } + if (byId("filter_button").exists()) { + tap("filter_button"); + byId("users_filter_bottom_sheet").shouldBe(Condition.visible, Duration.ofSeconds(15)); + return; + } + if (byId("action_filter").exists()) { + tap("action_filter"); + byId("users_filter_bottom_sheet").shouldBe(Condition.visible, Duration.ofSeconds(15)); + return; + } + SelenideElement button = byUiAutomator("new UiSelector().descriptionContains(\"Фильтр\")"); + if (button.exists()) { + button.click(); + byId("users_filter_bottom_sheet").shouldBe(Condition.visible, Duration.ofSeconds(15)); + return; + } + button = byUiAutomator("new UiSelector().descriptionContains(\"Filter\")"); + if (button.exists()) { + button.click(); + byId("users_filter_bottom_sheet").shouldBe(Condition.visible, Duration.ofSeconds(15)); + return; + } + throw new IllegalStateException("Filter button was not found on Users screen."); + } + + private SelenideElement filterInput() { + if (byId("filter_input").exists()) { + return byId("filter_input"); + } + if (byId("username_filter_input").exists()) { + return byId("username_filter_input"); + } + if (byId("users_filter_input").exists()) { + return byId("users_filter_input"); + } + if (byId("username_input").exists()) { + return byId("username_input"); + } + if (byId("search_src_text").exists()) { + return byId("search_src_text"); + } + if (byId("query_text").exists()) { + return byId("query_text"); + } + String appPackage = config().appPackage(); + SelenideElement editText = byXpath( + "//android.view.ViewGroup[@resource-id='" + appPackage + ":id/users_filter_bottom_sheet']" + + "//android.widget.EditText" + ); + if (editText.exists()) { + return editText; + } + throw new IllegalStateException("Filter input was not found on Users screen."); + } + + private void applyFilter() { + if (byId("apply_button").exists()) { + tap("apply_button"); + return; + } + if (byId("apply").exists()) { + tap("apply"); + return; + } + if (byText("ПРИМЕНИТЬ").exists()) { + byText("ПРИМЕНИТЬ").click(); + return; + } + if (byText("APPLY").exists()) { + byText("APPLY").click(); + return; + } + throw new IllegalStateException("Apply filter button was not found on Users filter screen."); + } } diff --git a/src/main/java/ru/otus/mobile/page/WishlistsPage.java b/src/main/java/ru/otus/mobile/page/WishlistsPage.java index e8bde65..1d38b8e 100644 --- a/src/main/java/ru/otus/mobile/page/WishlistsPage.java +++ b/src/main/java/ru/otus/mobile/page/WishlistsPage.java @@ -1,11 +1,14 @@ package ru.otus.mobile.page; import com.codeborne.selenide.Condition; +import com.codeborne.selenide.CollectionCondition; import com.google.inject.Inject; import ru.otus.mobile.component.BottomNavigationComponent; import ru.otus.mobile.component.WishlistFormComponent; import ru.otus.mobile.config.MobileConfig; +import java.time.Duration; + public final class WishlistsPage extends AbsBasePage { private final WishlistFormComponent form; @@ -39,6 +42,14 @@ public final class WishlistsPage extends AbsBasePage { scrollToText(name).click(); } + public void openFirstWishlist() { + allById("wishlist_item") + .shouldHave(CollectionCondition.sizeGreaterThan(0), Duration.ofSeconds(15)) + .first() + .shouldBe(Condition.visible, Duration.ofSeconds(15)) + .click(); + } + private void ensureWishlistsList() { if (exists("wishlist_item") || exists("wishlists")) { return; diff --git a/src/test/java/ru/otus/mobile/tests/GiftsTest.java b/src/test/java/ru/otus/mobile/tests/GiftsTest.java index 455b419..2080682 100644 --- a/src/test/java/ru/otus/mobile/tests/GiftsTest.java +++ b/src/test/java/ru/otus/mobile/tests/GiftsTest.java @@ -6,8 +6,11 @@ import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import ru.otus.mobile.annotations.MobileUser; import ru.otus.mobile.config.TestAccount; +import ru.otus.mobile.config.TestContext; +import ru.otus.mobile.db.DbClient; import ru.otus.mobile.extensions.MobileExtension; import ru.otus.mobile.page.GiftsPage; +import ru.otus.mobile.page.LoginPage; import ru.otus.mobile.page.WishlistsPage; import ru.otus.mobile.util.TestData; @@ -23,16 +26,23 @@ public class GiftsTest { @Inject private TestData testData; + @Inject + private DbClient dbClient; + + @Inject + private LoginPage loginPage; + @Test @DisplayName("Создание и редактирование подарка") - void createAndEditGift() { - String wishlistName = testData.uniqueName("Wishlist"); + void createAndEditGift(TestContext context) { + context.account().dataPreparation().accept(dbClient, context.account().username()); + loginPage.login(context.account()); + String name = testData.uniqueName("Gift"); String updated = name + " updated"; wishlistsPage.open(); - wishlistsPage.createWishlist(wishlistName); - wishlistsPage.openWishlist(wishlistName); + wishlistsPage.openFirstWishlist(); giftsPage.createGift(name); giftsPage.shouldSeeGift(name); giftsPage.editGift(name, updated); diff --git a/src/test/java/ru/otus/mobile/tests/ReservationTest.java b/src/test/java/ru/otus/mobile/tests/ReservationTest.java index a699ef0..e0c5cef 100644 --- a/src/test/java/ru/otus/mobile/tests/ReservationTest.java +++ b/src/test/java/ru/otus/mobile/tests/ReservationTest.java @@ -6,8 +6,10 @@ import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import ru.otus.mobile.annotations.MobileUser; import ru.otus.mobile.config.TestAccount; +import ru.otus.mobile.config.TestContext; import ru.otus.mobile.db.DbClient; import ru.otus.mobile.extensions.MobileExtension; +import ru.otus.mobile.page.LoginPage; import ru.otus.mobile.page.UsersPage; import static org.junit.jupiter.api.Assertions.assertNotEquals; @@ -15,25 +17,26 @@ import static org.junit.jupiter.api.Assertions.assertNotEquals; @ExtendWith(MobileExtension.class) @MobileUser(TestAccount.RESERVATION) public class ReservationTest { - private static final String OWNER_WISHLIST_NAME = "Owner Wishlist"; - private static final String OWNER_GIFT_NAME = "Owner Gift"; - @Inject private UsersPage usersPage; @Inject private DbClient dbClient; + @Inject + private LoginPage loginPage; + @Test @DisplayName("Изменение статуса резервирования подарка другого пользователя") - void changeReservationStatus() { + void changeReservationStatus(TestContext context) { + TestAccount.RESERVATION_OWNER.dataPreparation().accept(dbClient, TestAccount.RESERVATION_OWNER.username()); + loginPage.login(context.account()); + usersPage.open(); - String reservationOwner = usersPage.firstVisibleUsernameExcluding(TestAccount.RESERVATION.username()); - dbClient.prepareReservationData(reservationOwner, TestAccount.RESERVATION.username()); - usersPage.open(); - usersPage.openUser(reservationOwner); - usersPage.openWishlist(OWNER_WISHLIST_NAME); - usersPage.openGift(OWNER_GIFT_NAME); + usersPage.filterByUsername(TestAccount.RESERVATION_OWNER.username()); + usersPage.openUser(TestAccount.RESERVATION_OWNER.username()); + usersPage.openFirstWishlist(); + usersPage.openFirstGift(); boolean before = usersPage.isReserved(); usersPage.toggleReservation(); assertNotEquals(before, usersPage.isReserved(), "Reservation status was not changed"); diff --git a/src/test/java/ru/otus/mobile/tests/WishlistsTest.java b/src/test/java/ru/otus/mobile/tests/WishlistsTest.java index 4cfec77..5569604 100644 --- a/src/test/java/ru/otus/mobile/tests/WishlistsTest.java +++ b/src/test/java/ru/otus/mobile/tests/WishlistsTest.java @@ -6,7 +6,10 @@ import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import ru.otus.mobile.annotations.MobileUser; import ru.otus.mobile.config.TestAccount; +import ru.otus.mobile.config.TestContext; +import ru.otus.mobile.db.DbClient; import ru.otus.mobile.extensions.MobileExtension; +import ru.otus.mobile.page.LoginPage; import ru.otus.mobile.page.WishlistsPage; import ru.otus.mobile.util.TestData; @@ -19,9 +22,18 @@ public class WishlistsTest { @Inject private TestData testData; + @Inject + private DbClient dbClient; + + @Inject + private LoginPage loginPage; + @Test @DisplayName("Создание и редактирование списка желаний") - void createAndEditWishlist() { + void createAndEditWishlist(TestContext context) { + context.account().dataPreparation().accept(dbClient, context.account().username()); + loginPage.login(context.account()); + String name = testData.uniqueName("Wishlist"); String updated = name + " updated";