Compare commits

..

6 Commits

39 changed files with 673 additions and 519 deletions
+4
View File
@@ -0,0 +1,4 @@
DB_URL=jdbc:postgresql://<host>:<port>/<db>
DB_USER=<db_user>
DB_PASSWORD=<db_password>
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
+1
View File
@@ -5,3 +5,4 @@ build/
.DS_Store
allure-results/
logcat.txt
.env
+47 -19
View File
@@ -19,14 +19,6 @@
- `src/test/java` — только тестовые классы.
- `wiremock` — маппинги и APK.
## Тестовые аккаунты
- `user1us / user1us`
- `user2us / user2us`
- `user3us / user3us`
- `user4us / user4us`
`user4us` используется как владелец подарка в тесте резервирования.
## Запуск
1. Поднять окружение:
```bash
@@ -38,23 +30,59 @@ docker compose up -d
docker compose ps
```
3. Указать доступ к БД:
3. Подготовить переменные окружения (использовать один из вариантов):
Вариант A: через `.env`
- скопировать `.env.example` в `.env`;
- заполнить `.env` актуальными значениями вашей среды;
- загрузить `.env` в текущую shell-сессию.
PowerShell:
```powershell
$env:DB_URL="jdbc:postgresql://sql.otus.kartushin.su:5432/wishlist"
$env:DB_USER="student"
$env:DB_PASSWORD="student"
```
bash:
```bash
export DB_URL="jdbc:postgresql://sql.otus.kartushin.su:5432/wishlist"
export DB_USER="student"
export DB_PASSWORD="student"
Get-Content .env | Where-Object { $_ -match '^[^#].+=.+' } | ForEach-Object {
$name, $value = $_ -split '=', 2
[System.Environment]::SetEnvironmentVariable($name, $value, 'Process')
}
```
bash:
```bash
set -a
source .env
set +a
```
Вариант B: напрямую в shell-сессии
PowerShell:
```powershell
$env:DB_URL="jdbc:postgresql://<host>:<port>/<db>"
$env:DB_USER="<db_user>"
$env:DB_PASSWORD="<db_password>"
$env:MOBILE_HOST="127.0.0.1"
```
bash:
```bash
export DB_URL="jdbc:postgresql://<host>:<port>/<db>"
export DB_USER="<db_user>"
export DB_PASSWORD="<db_password>"
export MOBILE_HOST="127.0.0.1"
```
Оба варианта эквивалентны: тесты используют переменные окружения процесса.
Тестовые пользователи зафиксированы в коде (`TestAccount`):
- `user1us` — тест списков желаний;
- `user2us` — тест подарков;
- `user3us` — пользователь, который резервирует подарок;
- `user4us` — пользователь-владелец списка в тесте резервирования.
Для production-подобного запуска используйте шаблон `.env.production.example` и не коммитьте реальные значения в репозиторий.
Тесты запускаются параллельно по классам (2 потока) и распределяются по эмуляторам через очередь.
4. Запустить тесты:
```bash
mvn test
```
Тесты запускаются параллельно по классам (2 потока) и распределяются по эмуляторам через очередь.
Примечание: эмуляторы зафиксированы в enum `TestEmulator` (порты `4723` и `4725`), в конфигурации задается только хост (`MOBILE_HOST`).
@@ -1,25 +1,17 @@
package ru.otus.mobile.component;
import com.google.inject.Inject;
import ru.otus.mobile.config.MobileConfig;
import com.codeborne.selenide.SelenideElement;
public final class AlertDialogComponent extends BaseMobileComponent {
@Inject
public AlertDialogComponent(MobileConfig config) {
super(config);
private final SelenideElement positiveButton = byIdInRoot("android:id/button1");
public AlertDialogComponent(SelenideElement root) {
super(root);
}
public void acceptIfVisible() {
if (exists("android:id/button1")) {
tap("android:id/button1");
return;
}
if (byText("OK").exists()) {
byText("OK").click();
return;
}
if (byText("ОК").exists()) {
byText("ОК").click();
if (positiveButton.exists()) {
positiveButton.click();
}
}
}
@@ -1,10 +1,35 @@
package ru.otus.mobile.component;
import ru.otus.mobile.config.MobileConfig;
import com.codeborne.selenide.ElementsCollection;
import com.codeborne.selenide.SelenideElement;
import com.codeborne.selenide.WebElementCondition;
import io.appium.java_client.AppiumBy;
import ru.otus.mobile.page.AbsPageObject;
import java.time.Duration;
public abstract class BaseMobileComponent extends AbsPageObject {
protected BaseMobileComponent(MobileConfig config) {
super(config);
protected final SelenideElement root;
protected BaseMobileComponent(SelenideElement root) {
this.root = root;
}
protected SelenideElement byIdInRoot(String id) {
return root.$(AppiumBy.id(fullIdValue(id)));
}
protected ElementsCollection allByIdInRoot(String id) {
return root.$$(AppiumBy.id(fullIdValue(id)));
}
public BaseMobileComponent shouldBe(WebElementCondition... conditions) {
root.shouldBe(conditions);
return this;
}
public BaseMobileComponent shouldBe(WebElementCondition condition, Duration timeout) {
root.shouldBe(condition, timeout);
return this;
}
}
@@ -1,19 +1,20 @@
package ru.otus.mobile.component;
import com.google.inject.Inject;
import ru.otus.mobile.config.MobileConfig;
import com.codeborne.selenide.SelenideElement;
public final class BottomNavigationComponent extends BaseMobileComponent {
@Inject
public BottomNavigationComponent(MobileConfig config) {
super(config);
private final SelenideElement mineMenu = byIdInRoot("mine_menu");
private final SelenideElement usersMenu = byIdInRoot("users_menu");
public BottomNavigationComponent(SelenideElement root) {
super(root);
}
public void openWishlists() {
tap("mine_menu");
mineMenu.click();
}
public void openUsers() {
tap("users_menu");
usersMenu.click();
}
}
@@ -1,17 +1,23 @@
package ru.otus.mobile.component;
import com.google.inject.Inject;
import ru.otus.mobile.config.MobileConfig;
import com.codeborne.selenide.SelenideElement;
public final class GiftFormComponent extends BaseMobileComponent {
@Inject
public GiftFormComponent(MobileConfig config) {
super(config);
private final SelenideElement nameInput = byIdInRoot("name_input");
private final SelenideElement priceInput = byIdInRoot("price_input");
private final SelenideElement saveButton = byIdInRoot("save_button");
public GiftFormComponent(SelenideElement root) {
super(root);
}
public void save(String name, String price) {
type("name_input", name);
type("price_input", price);
tap("save_button");
nameInput.click();
nameInput.clear();
nameInput.sendKeys(name);
priceInput.click();
priceInput.clear();
priceInput.sendKeys(price);
saveButton.click();
}
}
@@ -0,0 +1,40 @@
package ru.otus.mobile.component;
import com.codeborne.selenide.Condition;
import com.codeborne.selenide.SelenideElement;
import static com.codeborne.selenide.Condition.text;
public final class GiftItemComponent extends BaseMobileComponent {
private final SelenideElement title = byIdInRoot("title");
private final SelenideElement editButton = byIdInRoot("edit_button");
private final SelenideElement reservedToggle = byIdInRoot("reserved");
public GiftItemComponent(SelenideElement root) {
super(root);
}
public String titleText() {
return title.getText();
}
public void shouldHaveTitle(String value) {
title.shouldHave(text(value));
}
public void open() {
title.click();
}
public void edit() {
editButton.click();
}
public boolean isReserved() {
return Boolean.parseBoolean(reservedToggle.shouldBe(Condition.visible).getAttribute("checked"));
}
public void toggleReservation() {
reservedToggle.shouldBe(Condition.visible).click();
}
}
@@ -0,0 +1,35 @@
package ru.otus.mobile.component;
import com.codeborne.selenide.CollectionCondition;
import com.codeborne.selenide.Condition;
import com.codeborne.selenide.ElementsCollection;
import com.codeborne.selenide.SelenideElement;
import java.time.Duration;
public final class GiftsContentComponent extends BaseMobileComponent {
private final ElementsCollection items = allByIdInRoot("gift_item");
public GiftsContentComponent(SelenideElement root) {
super(root);
}
public GiftItemComponent get(int index) {
return new GiftItemComponent(items.get(index).shouldBe(Condition.visible, Duration.ofSeconds(15)));
}
public GiftItemComponent first() {
return get(0);
}
public GiftItemComponent byTitle(String title) {
items.shouldHave(CollectionCondition.sizeGreaterThan(0), Duration.ofSeconds(15));
for (int i = 0; i < items.size(); i++) {
GiftItemComponent item = get(i);
if (title.equals(item.titleText())) {
return item;
}
}
throw new IllegalStateException("Gift with title '" + title + "' was not found.");
}
}
@@ -0,0 +1,15 @@
package ru.otus.mobile.component;
import com.codeborne.selenide.SelenideElement;
public final class TopBarComponent extends BaseMobileComponent {
private final SelenideElement filterButton = byIdInRoot("filter");
public TopBarComponent(SelenideElement root) {
super(root);
}
public void openUsersFilter() {
filterButton.click();
}
}
@@ -0,0 +1,19 @@
package ru.otus.mobile.component;
import com.codeborne.selenide.SelenideElement;
public final class UserItemComponent extends BaseMobileComponent {
private final SelenideElement username = byIdInRoot("username");
public UserItemComponent(SelenideElement root) {
super(root);
}
public String usernameText() {
return username.getText();
}
public void open() {
username.click();
}
}
@@ -0,0 +1,35 @@
package ru.otus.mobile.component;
import com.codeborne.selenide.CollectionCondition;
import com.codeborne.selenide.Condition;
import com.codeborne.selenide.ElementsCollection;
import com.codeborne.selenide.SelenideElement;
import java.time.Duration;
public final class UsersContentComponent extends BaseMobileComponent {
private final ElementsCollection items = allByIdInRoot("user_item");
public UsersContentComponent(SelenideElement root) {
super(root);
}
public UserItemComponent get(int index) {
return new UserItemComponent(items.get(index).shouldBe(Condition.visible, Duration.ofSeconds(15)));
}
public UserItemComponent first() {
return get(0);
}
public UserItemComponent byUsername(String username) {
items.shouldHave(CollectionCondition.sizeGreaterThan(0), Duration.ofSeconds(15));
for (int i = 0; i < items.size(); i++) {
UserItemComponent item = get(i);
if (username.equals(item.usernameText())) {
return item;
}
}
throw new IllegalStateException("User with username '" + username + "' was not found.");
}
}
@@ -0,0 +1,19 @@
package ru.otus.mobile.component;
import com.codeborne.selenide.SelenideElement;
public final class UsersFilterComponent extends BaseMobileComponent {
private final SelenideElement usernameInput = byIdInRoot("username_input");
private final SelenideElement applyButton = byIdInRoot("apply_button");
public UsersFilterComponent(SelenideElement root) {
super(root);
}
public void applyByUsername(String username) {
usernameInput.click();
usernameInput.clear();
usernameInput.sendKeys(username);
applyButton.click();
}
}
@@ -1,16 +1,19 @@
package ru.otus.mobile.component;
import com.google.inject.Inject;
import ru.otus.mobile.config.MobileConfig;
import com.codeborne.selenide.SelenideElement;
public final class WishlistFormComponent extends BaseMobileComponent {
@Inject
public WishlistFormComponent(MobileConfig config) {
super(config);
private final SelenideElement titleInput = byIdInRoot("title_input");
private final SelenideElement saveButton = byIdInRoot("save_button");
public WishlistFormComponent(SelenideElement root) {
super(root);
}
public void save(String title) {
type("title_input", title);
tap("save_button");
titleInput.click();
titleInput.clear();
titleInput.sendKeys(title);
saveButton.click();
}
}
@@ -0,0 +1,30 @@
package ru.otus.mobile.component;
import com.codeborne.selenide.SelenideElement;
import static com.codeborne.selenide.Condition.text;
public final class WishlistItemComponent extends BaseMobileComponent {
private final SelenideElement title = byIdInRoot("title");
private final SelenideElement editButton = byIdInRoot("edit_button");
public WishlistItemComponent(SelenideElement root) {
super(root);
}
public String titleText() {
return title.getText();
}
public void shouldHaveTitle(String value) {
title.shouldHave(text(value));
}
public void open() {
title.click();
}
public void edit() {
editButton.click();
}
}
@@ -0,0 +1,35 @@
package ru.otus.mobile.component;
import com.codeborne.selenide.CollectionCondition;
import com.codeborne.selenide.Condition;
import com.codeborne.selenide.ElementsCollection;
import com.codeborne.selenide.SelenideElement;
import java.time.Duration;
public final class WishlistsContentComponent extends BaseMobileComponent {
private final ElementsCollection items = allByIdInRoot("wishlist_item");
public WishlistsContentComponent(SelenideElement root) {
super(root);
}
public WishlistItemComponent get(int index) {
return new WishlistItemComponent(items.get(index).shouldBe(Condition.visible, Duration.ofSeconds(15)));
}
public WishlistItemComponent first() {
return get(0);
}
public WishlistItemComponent byTitle(String title) {
items.shouldHave(CollectionCondition.sizeGreaterThan(0), Duration.ofSeconds(15));
for (int i = 0; i < items.size(); i++) {
WishlistItemComponent item = get(i);
if (title.equals(item.titleText())) {
return item;
}
}
throw new IllegalStateException("Wishlist with title '" + title + "' was not found.");
}
}
@@ -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,11 +1,16 @@
package ru.otus.mobile.config;
import com.google.inject.Inject;
import com.google.inject.Singleton;
import java.nio.file.Path;
import java.nio.file.Paths;
@Singleton
public final class ProjectPaths {
private final Path logcatFile;
@Inject
public ProjectPaths() {
this.logcatFile = Paths.get("").toAbsolutePath().normalize().resolve("logcat.txt");
}
@@ -1,72 +1,23 @@
package ru.otus.mobile.config;
import ru.otus.mobile.db.DbClient;
import java.util.function.BiConsumer;
public enum TestAccount {
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("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("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 username;
private final String password;
private final String resetSql;
private final BiConsumer<DbClient, String> dataPreparation;
TestAccount(String username, String password, String resetSql) {
TestAccount(String username, String password, BiConsumer<DbClient, String> dataPreparation) {
this.username = username;
this.password = password;
this.resetSql = resetSql;
this.dataPreparation = dataPreparation;
}
public String username() {
@@ -77,7 +28,7 @@ public enum TestAccount {
return password;
}
public String resetSql() {
return resetSql;
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
);
}
}
+43 -15
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,26 +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);
throw new IllegalStateException("Failed to " + operation + " for username " + username, e);
}
}
}
@@ -1,13 +1,17 @@
package ru.otus.mobile.driver;
import com.google.inject.Inject;
import com.google.inject.Singleton;
import ru.otus.mobile.config.MobileConfig;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;
@Singleton
public final class EmulatorQueue {
private final BlockingQueue<MobileConfig.Emulator> queue;
@Inject
public EmulatorQueue(MobileConfig config) {
this.queue = new LinkedBlockingQueue<>(config.emulators());
}
@@ -1,5 +1,7 @@
package ru.otus.mobile.driver;
import com.google.inject.Inject;
import com.google.inject.Singleton;
import io.appium.java_client.android.AndroidDriver;
import org.openqa.selenium.logging.LogEntries;
import org.openqa.selenium.logging.LogEntry;
@@ -11,9 +13,11 @@ import java.nio.file.Files;
import java.nio.file.StandardOpenOption;
import java.time.Instant;
@Singleton
public final class LogcatCollector {
private final ProjectPaths projectPaths;
@Inject
public LogcatCollector(ProjectPaths projectPaths) {
this.projectPaths = projectPaths;
}
@@ -1,5 +1,7 @@
package ru.otus.mobile.driver;
import com.google.inject.Inject;
import com.google.inject.Singleton;
import io.appium.java_client.android.AndroidDriver;
import io.appium.java_client.android.options.UiAutomator2Options;
import ru.otus.mobile.config.MobileConfig;
@@ -7,7 +9,12 @@ import ru.otus.mobile.config.MobileConfig;
import java.net.MalformedURLException;
import java.net.URI;
@Singleton
public final class MobileDriverFactory {
@Inject
public MobileDriverFactory() {
}
public AndroidDriver create(MobileConfig.Emulator emulator) {
UiAutomator2Options options = new UiAutomator2Options()
.setPlatformName("Android")
@@ -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());
}
}
}
@@ -4,39 +4,23 @@ import com.google.inject.AbstractModule;
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 {
@Override
protected void configure() {
}
@Provides
@Singleton
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);
}
@Provides
@Singleton
ProjectPaths projectPaths() {
return new ProjectPaths();
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
@@ -48,24 +32,6 @@ public final class CoreModule extends AbstractModule {
return new DbConfig(url, user, password);
}
@Provides
@Singleton
EmulatorQueue emulatorQueue(MobileConfig config) {
return new EmulatorQueue(config);
}
@Provides
@Singleton
MobileDriverFactory mobileDriverFactory() {
return new MobileDriverFactory();
}
@Provides
@Singleton
LogcatCollector logcatCollector(ProjectPaths projectPaths) {
return new LogcatCollector(projectPaths);
}
private String requiredValue(String property, String env) {
String value = value(property, env, null);
if (value == 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();
}
}
@@ -1,19 +1,10 @@
package ru.otus.mobile.page;
import ru.otus.mobile.component.BottomNavigationComponent;
import ru.otus.mobile.config.MobileConfig;
import ru.otus.mobile.component.TopBarComponent;
public abstract class AbsBasePage extends AbsPageObject {
protected final BottomNavigationComponent bottomNavigation;
private final MobileConfig config;
protected AbsBasePage(MobileConfig config, BottomNavigationComponent bottomNavigation) {
super(config);
this.config = config;
this.bottomNavigation = bottomNavigation;
}
protected MobileConfig config() {
return config;
}
protected final BottomNavigationComponent bottomNavigation =
new BottomNavigationComponent(byId("bottom_navigation"));
protected final TopBarComponent topBar = new TopBarComponent(byId("top_app_bar"));
}
@@ -1,103 +1,34 @@
package ru.otus.mobile.page;
import com.codeborne.selenide.CollectionCondition;
import com.codeborne.selenide.Condition;
import com.codeborne.selenide.SelenideElement;
import com.codeborne.selenide.WebDriverRunner;
import com.codeborne.selenide.appium.SelenideAppiumCollection;
import io.appium.java_client.AppiumBy;
import ru.otus.mobile.config.MobileConfig;
import static com.codeborne.selenide.appium.SelenideAppium.$$;
import static com.codeborne.selenide.appium.SelenideAppium.$;
public abstract class AbsPageObject {
private final MobileConfig config;
protected AbsPageObject(MobileConfig config) {
this.config = config;
}
private static final String APP_PACKAGE_PROPERTY = "app.package";
private static final String DEFAULT_APP_PACKAGE = "ru.otus.wishlist";
protected SelenideElement byId(String id) {
return $(AppiumBy.id(fullId(id)));
return $(AppiumBy.id(fullIdValue(id)));
}
protected SelenideAppiumCollection allById(String id) {
return $$(AppiumBy.id(fullId(id)));
}
protected SelenideElement byText(String text) {
return $(AppiumBy.androidUIAutomator("new UiSelector().text(\"" + escape(text) + "\")"));
}
protected SelenideElement byTextContains(String text) {
return $(AppiumBy.androidUIAutomator("new UiSelector().textContains(\"" + escape(text) + "\")"));
}
protected SelenideElement byUiAutomator(String selector) {
return $(AppiumBy.androidUIAutomator(selector));
}
protected SelenideElement byXpath(String xpath) {
return $(AppiumBy.xpath(xpath));
}
protected void type(String id, String value) {
SelenideElement input = byId(id).shouldBe(Condition.visible);
input.click();
input.clear();
input.sendKeys(value);
}
protected void tap(String id) {
byId(id).shouldBe(Condition.visible).click();
}
protected boolean exists(String id) {
return byId(id).exists();
}
protected void shouldHaveItems(String id) {
allById(id).shouldHave(CollectionCondition.sizeGreaterThan(0));
}
protected SelenideElement scrollToText(String text) {
String selector = "new UiScrollable(new UiSelector().scrollable(true))"
+ ".scrollIntoView(new UiSelector().textContains(\"" + escape(text) + "\"))";
return $(AppiumBy.androidUIAutomator(selector));
return $$(AppiumBy.id(fullIdValue(id)));
}
protected void back() {
WebDriverRunner.getWebDriver().navigate().back();
}
protected String xpathLiteral(String value) {
if (!value.contains("\"")) {
return "\"" + value + "\"";
}
if (!value.contains("'")) {
return "'" + value + "'";
}
String[] parts = value.split("\"");
StringBuilder builder = new StringBuilder("concat(");
for (int i = 0; i < parts.length; i++) {
if (i > 0) {
builder.append(", '\"', ");
}
builder.append("\"").append(parts[i]).append("\"");
}
builder.append(")");
return builder.toString();
}
private String fullId(String id) {
protected static String fullIdValue(String id) {
if (id.contains(":")) {
return id;
}
return config.appPackage() + ":id/" + id;
}
private String escape(String value) {
return value.replace("\"", "\\\"");
String appPackage = System.getProperty(APP_PACKAGE_PROPERTY, DEFAULT_APP_PACKAGE);
return appPackage + ":id/" + id;
}
}
@@ -1,36 +1,36 @@
package ru.otus.mobile.page;
import com.codeborne.selenide.Condition;
import com.google.inject.Inject;
import ru.otus.mobile.component.BottomNavigationComponent;
import com.codeborne.selenide.SelenideElement;
import com.google.inject.Singleton;
import ru.otus.mobile.component.GiftFormComponent;
import ru.otus.mobile.config.MobileConfig;
import ru.otus.mobile.component.GiftsContentComponent;
@Singleton
public final class GiftsPage extends AbsBasePage {
private final GiftFormComponent form;
@Inject
public GiftsPage(MobileConfig config, BottomNavigationComponent bottomNavigation, GiftFormComponent form) {
super(config, bottomNavigation);
this.form = form;
}
private final SelenideElement addButton = byId("add_button");
private final GiftsContentComponent content = new GiftsContentComponent(byId("gifts_content"));
private final GiftFormComponent form = new GiftFormComponent(byId("gift_edit_bottom_sheet"));
public void createGift(String name) {
tap("add_button");
addButton.click();
form.save(name, "100");
content.shouldBe(Condition.visible);
}
public void editGift(String oldName, String newName) {
openGift(oldName);
tap("edit_button");
content.byTitle(oldName).edit();
form.save(newName, "100");
content.shouldBe(Condition.visible);
}
public void shouldSeeGift(String name) {
scrollToText(name).shouldBe(Condition.visible);
content.shouldBe(Condition.visible);
content.byTitle(name).shouldHaveTitle(name);
}
private void openGift(String name) {
scrollToText(name).click();
content.byTitle(name).open();
}
}
@@ -1,30 +1,33 @@
package ru.otus.mobile.page;
import com.google.inject.Inject;
import com.codeborne.selenide.SelenideElement;
import com.google.inject.Singleton;
import ru.otus.mobile.component.AlertDialogComponent;
import ru.otus.mobile.config.MobileConfig;
import ru.otus.mobile.config.TestAccount;
@Singleton
public final class LoginPage extends AbsPageObject {
private final AlertDialogComponent alertDialog;
@Inject
public LoginPage(MobileConfig config, AlertDialogComponent alertDialog) {
super(config);
this.alertDialog = alertDialog;
}
private final SelenideElement usernameInput = byId("username_text_input");
private final SelenideElement passwordInput = byId("password_text_input");
private final SelenideElement loginButton = byId("log_in_button");
private final SelenideElement appMainContainer = byId("app_main_fragment_container");
private final AlertDialogComponent alertDialog = new AlertDialogComponent(appMainContainer);
public void login(TestAccount account) {
if (!isOpened()) {
return;
}
type("username_text_input", account.username());
type("password_text_input", account.password());
tap("log_in_button");
usernameInput.click();
usernameInput.clear();
usernameInput.sendKeys(account.username());
passwordInput.click();
passwordInput.clear();
passwordInput.sendKeys(account.password());
loginButton.click();
alertDialog.acceptIfVisible();
}
public boolean isOpened() {
return exists("username_text_input") && exists("password_text_input");
return usernameInput.exists() && passwordInput.exists();
}
}
@@ -1,43 +1,67 @@
package ru.otus.mobile.page;
import com.codeborne.selenide.Condition;
import com.google.inject.Inject;
import ru.otus.mobile.component.BottomNavigationComponent;
import ru.otus.mobile.config.MobileConfig;
import com.google.inject.Singleton;
import ru.otus.mobile.component.GiftItemComponent;
import ru.otus.mobile.component.GiftsContentComponent;
import ru.otus.mobile.component.UsersContentComponent;
import ru.otus.mobile.component.UsersFilterComponent;
import ru.otus.mobile.component.WishlistsContentComponent;
import java.time.Duration;
@Singleton
public final class UsersPage extends AbsBasePage {
@Inject
public UsersPage(MobileConfig config, BottomNavigationComponent bottomNavigation) {
super(config, bottomNavigation);
}
private final UsersContentComponent usersContent = new UsersContentComponent(byId("users_content"));
private final UsersFilterComponent usersFilter = new UsersFilterComponent(byId("users_filter_bottom_sheet"));
private final WishlistsContentComponent wishlistsContent = new WishlistsContentComponent(byId("wishlists_content"));
private final GiftsContentComponent giftsContent = new GiftsContentComponent(byId("gifts_content"));
private GiftItemComponent currentGift;
public void open() {
bottomNavigation.openUsers();
byId("users_content").shouldBe(Condition.visible);
usersContent.shouldBe(Condition.visible, Duration.ofSeconds(15));
}
public void filterByUsername(String username) {
topBar.openUsersFilter();
usersFilter.shouldBe(Condition.visible, Duration.ofSeconds(15));
usersFilter.applyByUsername(username);
usersContent.byUsername(username);
}
public void openUser(String username) {
String appPackage = config().appPackage();
String selector = "new UiScrollable(new UiSelector().resourceId(\"" + appPackage + ":id/users\"))"
+ ".setMaxSearchSwipes(12)"
+ ".scrollIntoView(new UiSelector().resourceId(\"" + appPackage + ":id/username\")"
+ ".textContains(\"" + username.replace("\"", "\\\"") + "\"))";
byUiAutomator(selector).shouldBe(Condition.visible).click();
usersContent.byUsername(username).open();
}
public void openFirstWishlist() {
allById("wishlist_item").first().shouldBe(Condition.visible).click();
wishlistsContent.get(0).open();
}
public void openWishlist(String name) {
wishlistsContent.byTitle(name).open();
}
public void openFirstGift() {
allById("gift_item").first().shouldBe(Condition.visible).click();
currentGift = giftsContent.get(0);
}
public void openGift(String name) {
currentGift = giftsContent.byTitle(name);
}
public boolean isReserved() {
return Boolean.parseBoolean(byId("reserved").shouldBe(Condition.visible).getAttribute("checked"));
return selectedGift().isReserved();
}
public void toggleReservation() {
tap("reserved");
selectedGift().toggleReservation();
}
private GiftItemComponent selectedGift() {
if (currentGift == null) {
throw new IllegalStateException("Gift is not selected. Call openFirstGift/openGift first.");
}
return currentGift;
}
}
@@ -1,61 +1,43 @@
package ru.otus.mobile.page;
import com.codeborne.selenide.Condition;
import com.google.inject.Inject;
import ru.otus.mobile.component.BottomNavigationComponent;
import com.codeborne.selenide.SelenideElement;
import com.google.inject.Singleton;
import ru.otus.mobile.component.WishlistsContentComponent;
import ru.otus.mobile.component.WishlistFormComponent;
import ru.otus.mobile.config.MobileConfig;
import java.time.Duration;
@Singleton
public final class WishlistsPage extends AbsBasePage {
private final WishlistFormComponent form;
@Inject
public WishlistsPage(MobileConfig config, BottomNavigationComponent bottomNavigation, WishlistFormComponent form) {
super(config, bottomNavigation);
this.form = form;
}
private final SelenideElement addButton = byId("add_button");
private final WishlistsContentComponent content = new WishlistsContentComponent(byId("wishlists_content"));
private final WishlistFormComponent form = new WishlistFormComponent(byId("wishlist_edit_bottom_sheet"));
public void open() {
bottomNavigation.openWishlists();
byId("wishlists_content").shouldBe(Condition.visible);
content.shouldBe(Condition.visible, Duration.ofSeconds(15));
}
public void createWishlist(String name) {
tap("add_button");
addButton.click();
form.save(name);
}
public void editWishlist(String oldName, String newName) {
ensureWishlistsList();
editButtonFor(oldName).shouldBe(Condition.visible).click();
content.byTitle(oldName).edit();
form.save(newName);
}
public void shouldSeeWishlist(String name) {
scrollToText(name).shouldBe(Condition.visible);
content.byTitle(name).shouldHaveTitle(name);
}
public void openWishlist(String name) {
scrollToText(name).click();
content.byTitle(name).open();
}
private void ensureWishlistsList() {
if (exists("wishlist_item") || exists("wishlists")) {
return;
}
if (exists("gifts_content") || exists("gift_item") || exists("add_button")) {
back();
}
byId("wishlists_content").shouldBe(Condition.visible);
}
private com.codeborne.selenide.SelenideElement editButtonFor(String name) {
String full = config().appPackage();
String xpath = "//androidx.recyclerview.widget.RecyclerView[@resource-id='" + full + ":id/wishlists']"
+ "//android.view.ViewGroup[@resource-id='" + full + ":id/wishlist_item'"
+ " and .//android.widget.TextView[@resource-id='" + full + ":id/title'"
+ " and contains(@text," + xpathLiteral(name) + ")]]"
+ "//android.widget.Button[@resource-id='" + full + ":id/edit_button']";
return byXpath(xpath);
public void openFirstWishlist() {
content.first().open();
}
}
@@ -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);
@@ -5,9 +5,11 @@ import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import ru.otus.mobile.annotations.MobileUser;
import ru.otus.mobile.config.MobileConfig;
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;
@@ -19,13 +21,20 @@ public class ReservationTest {
private UsersPage usersPage;
@Inject
private MobileConfig mobileConfig;
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();
usersPage.openUser(mobileConfig.reservationOwnerUsername());
usersPage.filterByUsername(TestAccount.RESERVATION_OWNER.username());
usersPage.openUser(TestAccount.RESERVATION_OWNER.username());
usersPage.openFirstWishlist();
usersPage.openFirstGift();
boolean before = usersPage.isReserved();
@@ -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";