Simplify mobile test architecture and stabilize data setup

This commit is contained in:
2026-04-18 03:27:16 +03:00
parent aaf63cc438
commit 46f3de4d55
17 changed files with 326 additions and 338 deletions
+1 -8
View File
@@ -1,11 +1,4 @@
DB_URL=jdbc:postgresql://<host>:<port>/<db>
DB_USER=<db_user>
DB_PASSWORD=<db_password>
WISHLISTS_USERNAME=<login_1>
WISHLISTS_PASSWORD=<password_1>
GIFTS_USERNAME=<login_2>
GIFTS_PASSWORD=<password_2>
RESERVATION_USERNAME=<login_3>
RESERVATION_PASSWORD=<password_3>
RESERVATION_OWNER=<login_owner>
MOBILE_HOST=127.0.0.1
+4
View File
@@ -0,0 +1,4 @@
DB_URL=jdbc:postgresql://sql.otus.kartushin.su:5432/wishlist
DB_USER=<prod_db_user>
DB_PASSWORD=<prod_db_password>
MOBILE_HOST=127.0.0.1
+11 -25
View File
@@ -59,13 +59,7 @@ PowerShell:
$env:DB_URL="jdbc:postgresql://<host>:<port>/<db>"
$env:DB_USER="<db_user>"
$env:DB_PASSWORD="<db_password>"
$env:WISHLISTS_USERNAME="<login_1>"
$env:WISHLISTS_PASSWORD="<password_1>"
$env:GIFTS_USERNAME="<login_2>"
$env:GIFTS_PASSWORD="<password_2>"
$env:RESERVATION_USERNAME="<login_3>"
$env:RESERVATION_PASSWORD="<password_3>"
$env:RESERVATION_OWNER="<login_owner>"
$env:MOBILE_HOST="127.0.0.1"
```
bash:
@@ -73,30 +67,22 @@ bash:
export DB_URL="jdbc:postgresql://<host>:<port>/<db>"
export DB_USER="<db_user>"
export DB_PASSWORD="<db_password>"
export WISHLISTS_USERNAME="<login_1>"
export WISHLISTS_PASSWORD="<password_1>"
export GIFTS_USERNAME="<login_2>"
export GIFTS_PASSWORD="<password_2>"
export RESERVATION_USERNAME="<login_3>"
export RESERVATION_PASSWORD="<password_3>"
export RESERVATION_OWNER="<login_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`).
@@ -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<Emulator> emulators;
public MobileConfig(
String appPackage,
String appUrl,
String reservationOwnerUsername,
String appiumHost,
List<Emulator> 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<Emulator> emulators() {
return emulators;
}
public record Emulator(String id, String appiumUrl, String deviceName, String appUrl) {
public static List<Emulator> parse(String rawValue, String defaultAppUrl) {
List<Emulator> 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) { }
}
@@ -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<DbClient, String> 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<DbClient, String> 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<DbClient, String> dataPreparation() {
return dataPreparation;
}
}
@@ -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
);
}
}
+42 -25
View File
@@ -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());
}
statement.execute();
} catch (SQLException e) {
throw new IllegalStateException("Failed to prepare test data for account " + account.name(), e);
}
public void clearWishlistsByUsername(String username) {
executeUpdate("""
DELETE FROM wishlists
WHERE user_id IN (SELECT id FROM users WHERE username = ?)
""", username, "clear wishlists");
}
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();
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();
}
} 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);
}
}
}
@@ -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());
}
}
}
@@ -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);
}
}
@@ -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());
}
}
}
@@ -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<MobileConfig.Emulator> 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<MobileConfig.Emulator> emulators = Arrays.stream(TestEmulator.values())
.map(emulator -> emulator.toEmulator(appiumHost, appUrl))
.toList();
return new MobileConfig(appPackage, appUrl, appiumHost, emulators);
}
@Provides
@@ -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();
}
}
@@ -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.");
}
}
@@ -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;
@@ -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);
@@ -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");
@@ -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";