Simplify mobile test architecture and stabilize data setup
This commit is contained in:
+1
-8
@@ -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
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -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";
|
||||
|
||||
|
||||
Reference in New Issue
Block a user