Compare commits
6 Commits
6a19b574b8
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
| e731a73838 | |||
| 97c58a29e0 | |||
| bcf831ad99 | |||
| 46f3de4d55 | |||
| aaf63cc438 | |||
| 0cd4e29d02 |
@@ -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
|
||||||
@@ -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
|
||||||
@@ -5,3 +5,4 @@ build/
|
|||||||
.DS_Store
|
.DS_Store
|
||||||
allure-results/
|
allure-results/
|
||||||
logcat.txt
|
logcat.txt
|
||||||
|
.env
|
||||||
|
|||||||
@@ -19,14 +19,6 @@
|
|||||||
- `src/test/java` — только тестовые классы.
|
- `src/test/java` — только тестовые классы.
|
||||||
- `wiremock` — маппинги и APK.
|
- `wiremock` — маппинги и APK.
|
||||||
|
|
||||||
## Тестовые аккаунты
|
|
||||||
- `user1us / user1us`
|
|
||||||
- `user2us / user2us`
|
|
||||||
- `user3us / user3us`
|
|
||||||
- `user4us / user4us`
|
|
||||||
|
|
||||||
`user4us` используется как владелец подарка в тесте резервирования.
|
|
||||||
|
|
||||||
## Запуск
|
## Запуск
|
||||||
1. Поднять окружение:
|
1. Поднять окружение:
|
||||||
```bash
|
```bash
|
||||||
@@ -38,23 +30,59 @@ docker compose up -d
|
|||||||
docker compose ps
|
docker compose ps
|
||||||
```
|
```
|
||||||
|
|
||||||
3. Указать доступ к БД:
|
3. Подготовить переменные окружения (использовать один из вариантов):
|
||||||
|
|
||||||
|
Вариант A: через `.env`
|
||||||
|
- скопировать `.env.example` в `.env`;
|
||||||
|
- заполнить `.env` актуальными значениями вашей среды;
|
||||||
|
- загрузить `.env` в текущую shell-сессию.
|
||||||
|
|
||||||
PowerShell:
|
PowerShell:
|
||||||
```powershell
|
```powershell
|
||||||
$env:DB_URL="jdbc:postgresql://sql.otus.kartushin.su:5432/wishlist"
|
Get-Content .env | Where-Object { $_ -match '^[^#].+=.+' } | ForEach-Object {
|
||||||
$env:DB_USER="student"
|
$name, $value = $_ -split '=', 2
|
||||||
$env:DB_PASSWORD="student"
|
[System.Environment]::SetEnvironmentVariable($name, $value, 'Process')
|
||||||
```
|
}
|
||||||
bash:
|
|
||||||
```bash
|
|
||||||
export DB_URL="jdbc:postgresql://sql.otus.kartushin.su:5432/wishlist"
|
|
||||||
export DB_USER="student"
|
|
||||||
export DB_PASSWORD="student"
|
|
||||||
```
|
```
|
||||||
|
|
||||||
|
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. Запустить тесты:
|
4. Запустить тесты:
|
||||||
```bash
|
```bash
|
||||||
mvn test
|
mvn test
|
||||||
```
|
```
|
||||||
|
|
||||||
Тесты запускаются параллельно по классам (2 потока) и распределяются по эмуляторам через очередь.
|
Примечание: эмуляторы зафиксированы в enum `TestEmulator` (порты `4723` и `4725`), в конфигурации задается только хост (`MOBILE_HOST`).
|
||||||
|
|||||||
@@ -1,25 +1,17 @@
|
|||||||
package ru.otus.mobile.component;
|
package ru.otus.mobile.component;
|
||||||
|
|
||||||
import com.google.inject.Inject;
|
import com.codeborne.selenide.SelenideElement;
|
||||||
import ru.otus.mobile.config.MobileConfig;
|
|
||||||
|
|
||||||
public final class AlertDialogComponent extends BaseMobileComponent {
|
public final class AlertDialogComponent extends BaseMobileComponent {
|
||||||
@Inject
|
private final SelenideElement positiveButton = byIdInRoot("android:id/button1");
|
||||||
public AlertDialogComponent(MobileConfig config) {
|
|
||||||
super(config);
|
public AlertDialogComponent(SelenideElement root) {
|
||||||
|
super(root);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void acceptIfVisible() {
|
public void acceptIfVisible() {
|
||||||
if (exists("android:id/button1")) {
|
if (positiveButton.exists()) {
|
||||||
tap("android:id/button1");
|
positiveButton.click();
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (byText("OK").exists()) {
|
|
||||||
byText("OK").click();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (byText("ОК").exists()) {
|
|
||||||
byText("ОК").click();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,10 +1,35 @@
|
|||||||
package ru.otus.mobile.component;
|
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 ru.otus.mobile.page.AbsPageObject;
|
||||||
|
|
||||||
|
import java.time.Duration;
|
||||||
|
|
||||||
public abstract class BaseMobileComponent extends AbsPageObject {
|
public abstract class BaseMobileComponent extends AbsPageObject {
|
||||||
protected BaseMobileComponent(MobileConfig config) {
|
protected final SelenideElement root;
|
||||||
super(config);
|
|
||||||
|
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;
|
package ru.otus.mobile.component;
|
||||||
|
|
||||||
import com.google.inject.Inject;
|
import com.codeborne.selenide.SelenideElement;
|
||||||
import ru.otus.mobile.config.MobileConfig;
|
|
||||||
|
|
||||||
public final class BottomNavigationComponent extends BaseMobileComponent {
|
public final class BottomNavigationComponent extends BaseMobileComponent {
|
||||||
@Inject
|
private final SelenideElement mineMenu = byIdInRoot("mine_menu");
|
||||||
public BottomNavigationComponent(MobileConfig config) {
|
private final SelenideElement usersMenu = byIdInRoot("users_menu");
|
||||||
super(config);
|
|
||||||
|
public BottomNavigationComponent(SelenideElement root) {
|
||||||
|
super(root);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void openWishlists() {
|
public void openWishlists() {
|
||||||
tap("mine_menu");
|
mineMenu.click();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void openUsers() {
|
public void openUsers() {
|
||||||
tap("users_menu");
|
usersMenu.click();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,17 +1,23 @@
|
|||||||
package ru.otus.mobile.component;
|
package ru.otus.mobile.component;
|
||||||
|
|
||||||
import com.google.inject.Inject;
|
import com.codeborne.selenide.SelenideElement;
|
||||||
import ru.otus.mobile.config.MobileConfig;
|
|
||||||
|
|
||||||
public final class GiftFormComponent extends BaseMobileComponent {
|
public final class GiftFormComponent extends BaseMobileComponent {
|
||||||
@Inject
|
private final SelenideElement nameInput = byIdInRoot("name_input");
|
||||||
public GiftFormComponent(MobileConfig config) {
|
private final SelenideElement priceInput = byIdInRoot("price_input");
|
||||||
super(config);
|
private final SelenideElement saveButton = byIdInRoot("save_button");
|
||||||
|
|
||||||
|
public GiftFormComponent(SelenideElement root) {
|
||||||
|
super(root);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void save(String name, String price) {
|
public void save(String name, String price) {
|
||||||
type("name_input", name);
|
nameInput.click();
|
||||||
type("price_input", price);
|
nameInput.clear();
|
||||||
tap("save_button");
|
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;
|
package ru.otus.mobile.component;
|
||||||
|
|
||||||
import com.google.inject.Inject;
|
import com.codeborne.selenide.SelenideElement;
|
||||||
import ru.otus.mobile.config.MobileConfig;
|
|
||||||
|
|
||||||
public final class WishlistFormComponent extends BaseMobileComponent {
|
public final class WishlistFormComponent extends BaseMobileComponent {
|
||||||
@Inject
|
private final SelenideElement titleInput = byIdInRoot("title_input");
|
||||||
public WishlistFormComponent(MobileConfig config) {
|
private final SelenideElement saveButton = byIdInRoot("save_button");
|
||||||
super(config);
|
|
||||||
|
public WishlistFormComponent(SelenideElement root) {
|
||||||
|
super(root);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void save(String title) {
|
public void save(String title) {
|
||||||
type("title_input", title);
|
titleInput.click();
|
||||||
tap("save_button");
|
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;
|
package ru.otus.mobile.config;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
public final class MobileConfig {
|
public final class MobileConfig {
|
||||||
private final String appPackage;
|
private final String appPackage;
|
||||||
private final String appUrl;
|
private final String appUrl;
|
||||||
private final String reservationOwnerUsername;
|
private final String appiumHost;
|
||||||
private final List<Emulator> emulators;
|
private final List<Emulator> emulators;
|
||||||
|
|
||||||
public MobileConfig(
|
public MobileConfig(
|
||||||
String appPackage,
|
String appPackage,
|
||||||
String appUrl,
|
String appUrl,
|
||||||
String reservationOwnerUsername,
|
String appiumHost,
|
||||||
List<Emulator> emulators
|
List<Emulator> emulators
|
||||||
) {
|
) {
|
||||||
this.appPackage = appPackage;
|
this.appPackage = appPackage;
|
||||||
this.appUrl = appUrl;
|
this.appUrl = appUrl;
|
||||||
this.reservationOwnerUsername = reservationOwnerUsername;
|
this.appiumHost = appiumHost;
|
||||||
this.emulators = List.copyOf(emulators);
|
this.emulators = List.copyOf(emulators);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -29,33 +28,13 @@ public final class MobileConfig {
|
|||||||
return appUrl;
|
return appUrl;
|
||||||
}
|
}
|
||||||
|
|
||||||
public String reservationOwnerUsername() {
|
public String appiumHost() {
|
||||||
return reservationOwnerUsername;
|
return appiumHost;
|
||||||
}
|
}
|
||||||
|
|
||||||
public List<Emulator> emulators() {
|
public List<Emulator> emulators() {
|
||||||
return emulators;
|
return emulators;
|
||||||
}
|
}
|
||||||
|
|
||||||
public record Emulator(String id, String appiumUrl, String deviceName, String appUrl) {
|
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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,11 +1,16 @@
|
|||||||
package ru.otus.mobile.config;
|
package ru.otus.mobile.config;
|
||||||
|
|
||||||
|
import com.google.inject.Inject;
|
||||||
|
import com.google.inject.Singleton;
|
||||||
|
|
||||||
import java.nio.file.Path;
|
import java.nio.file.Path;
|
||||||
import java.nio.file.Paths;
|
import java.nio.file.Paths;
|
||||||
|
|
||||||
|
@Singleton
|
||||||
public final class ProjectPaths {
|
public final class ProjectPaths {
|
||||||
private final Path logcatFile;
|
private final Path logcatFile;
|
||||||
|
|
||||||
|
@Inject
|
||||||
public ProjectPaths() {
|
public ProjectPaths() {
|
||||||
this.logcatFile = Paths.get("").toAbsolutePath().normalize().resolve("logcat.txt");
|
this.logcatFile = Paths.get("").toAbsolutePath().normalize().resolve("logcat.txt");
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,72 +1,23 @@
|
|||||||
package ru.otus.mobile.config;
|
package ru.otus.mobile.config;
|
||||||
|
|
||||||
|
import ru.otus.mobile.db.DbClient;
|
||||||
|
|
||||||
|
import java.util.function.BiConsumer;
|
||||||
|
|
||||||
public enum TestAccount {
|
public enum TestAccount {
|
||||||
WISHLISTS("user1us", "user1us", """
|
WISHLISTS("user1us", "user1us", DbClient::clearWishlistsByUsername),
|
||||||
WITH target_user AS (
|
GIFTS("user2us", "user2us", DbClient::clearGiftsByUsername),
|
||||||
SELECT id FROM users WHERE username = ?
|
RESERVATION("user3us", "user3us", DbClient::doNothing),
|
||||||
),
|
RESERVATION_OWNER("user4us", "user4us", DbClient::clearReservationByUsername);
|
||||||
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;
|
|
||||||
""");
|
|
||||||
|
|
||||||
private final String username;
|
private final String username;
|
||||||
private final String password;
|
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.username = username;
|
||||||
this.password = password;
|
this.password = password;
|
||||||
this.resetSql = resetSql;
|
this.dataPreparation = dataPreparation;
|
||||||
}
|
}
|
||||||
|
|
||||||
public String username() {
|
public String username() {
|
||||||
@@ -77,7 +28,7 @@ public enum TestAccount {
|
|||||||
return password;
|
return password;
|
||||||
}
|
}
|
||||||
|
|
||||||
public String resetSql() {
|
public BiConsumer<DbClient, String> dataPreparation() {
|
||||||
return resetSql;
|
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;
|
package ru.otus.mobile.db;
|
||||||
|
|
||||||
import com.google.inject.Inject;
|
import com.google.inject.Inject;
|
||||||
import ru.otus.mobile.config.MobileConfig;
|
|
||||||
import ru.otus.mobile.config.TestAccount;
|
|
||||||
|
|
||||||
import java.sql.Connection;
|
import java.sql.Connection;
|
||||||
import java.sql.DriverManager;
|
import java.sql.DriverManager;
|
||||||
@@ -11,26 +9,56 @@ import java.sql.SQLException;
|
|||||||
|
|
||||||
public final class DbClient {
|
public final class DbClient {
|
||||||
private final DbConfig config;
|
private final DbConfig config;
|
||||||
private final MobileConfig mobileConfig;
|
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
public DbClient(DbConfig config, MobileConfig mobileConfig) {
|
public DbClient(DbConfig config) {
|
||||||
this.config = config;
|
this.config = config;
|
||||||
this.mobileConfig = mobileConfig;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void resetData(TestAccount account) {
|
public void clearWishlistsByUsername(String username) {
|
||||||
try (Connection connection = DriverManager.getConnection(config.url(), config.user(), config.password());
|
executeUpdate("""
|
||||||
PreparedStatement statement = connection.prepareStatement(account.resetSql())) {
|
DELETE FROM wishlists
|
||||||
if (account == TestAccount.RESERVATION) {
|
WHERE user_id IN (SELECT id FROM users WHERE username = ?)
|
||||||
statement.setString(1, mobileConfig.reservationOwnerUsername());
|
""", username, "clear wishlists");
|
||||||
statement.setString(2, account.username());
|
}
|
||||||
} else {
|
|
||||||
statement.setString(1, account.username());
|
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) {
|
} 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;
|
package ru.otus.mobile.driver;
|
||||||
|
|
||||||
|
import com.google.inject.Inject;
|
||||||
|
import com.google.inject.Singleton;
|
||||||
import ru.otus.mobile.config.MobileConfig;
|
import ru.otus.mobile.config.MobileConfig;
|
||||||
|
|
||||||
import java.util.concurrent.BlockingQueue;
|
import java.util.concurrent.BlockingQueue;
|
||||||
import java.util.concurrent.LinkedBlockingQueue;
|
import java.util.concurrent.LinkedBlockingQueue;
|
||||||
|
|
||||||
|
@Singleton
|
||||||
public final class EmulatorQueue {
|
public final class EmulatorQueue {
|
||||||
private final BlockingQueue<MobileConfig.Emulator> queue;
|
private final BlockingQueue<MobileConfig.Emulator> queue;
|
||||||
|
|
||||||
|
@Inject
|
||||||
public EmulatorQueue(MobileConfig config) {
|
public EmulatorQueue(MobileConfig config) {
|
||||||
this.queue = new LinkedBlockingQueue<>(config.emulators());
|
this.queue = new LinkedBlockingQueue<>(config.emulators());
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
package ru.otus.mobile.driver;
|
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.AndroidDriver;
|
||||||
import org.openqa.selenium.logging.LogEntries;
|
import org.openqa.selenium.logging.LogEntries;
|
||||||
import org.openqa.selenium.logging.LogEntry;
|
import org.openqa.selenium.logging.LogEntry;
|
||||||
@@ -11,9 +13,11 @@ import java.nio.file.Files;
|
|||||||
import java.nio.file.StandardOpenOption;
|
import java.nio.file.StandardOpenOption;
|
||||||
import java.time.Instant;
|
import java.time.Instant;
|
||||||
|
|
||||||
|
@Singleton
|
||||||
public final class LogcatCollector {
|
public final class LogcatCollector {
|
||||||
private final ProjectPaths projectPaths;
|
private final ProjectPaths projectPaths;
|
||||||
|
|
||||||
|
@Inject
|
||||||
public LogcatCollector(ProjectPaths projectPaths) {
|
public LogcatCollector(ProjectPaths projectPaths) {
|
||||||
this.projectPaths = projectPaths;
|
this.projectPaths = projectPaths;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
package ru.otus.mobile.driver;
|
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.AndroidDriver;
|
||||||
import io.appium.java_client.android.options.UiAutomator2Options;
|
import io.appium.java_client.android.options.UiAutomator2Options;
|
||||||
import ru.otus.mobile.config.MobileConfig;
|
import ru.otus.mobile.config.MobileConfig;
|
||||||
@@ -7,7 +9,12 @@ import ru.otus.mobile.config.MobileConfig;
|
|||||||
import java.net.MalformedURLException;
|
import java.net.MalformedURLException;
|
||||||
import java.net.URI;
|
import java.net.URI;
|
||||||
|
|
||||||
|
@Singleton
|
||||||
public final class MobileDriverFactory {
|
public final class MobileDriverFactory {
|
||||||
|
@Inject
|
||||||
|
public MobileDriverFactory() {
|
||||||
|
}
|
||||||
|
|
||||||
public AndroidDriver create(MobileConfig.Emulator emulator) {
|
public AndroidDriver create(MobileConfig.Emulator emulator) {
|
||||||
UiAutomator2Options options = new UiAutomator2Options()
|
UiAutomator2Options options = new UiAutomator2Options()
|
||||||
.setPlatformName("Android")
|
.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;
|
package ru.otus.mobile.extensions;
|
||||||
|
|
||||||
|
import com.codeborne.selenide.WebDriverRunner;
|
||||||
import com.google.inject.Guice;
|
import com.google.inject.Guice;
|
||||||
import com.google.inject.Injector;
|
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.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.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.annotations.MobileUser;
|
||||||
|
import ru.otus.mobile.config.MobileConfig;
|
||||||
import ru.otus.mobile.config.TestAccount;
|
import ru.otus.mobile.config.TestAccount;
|
||||||
import ru.otus.mobile.db.DbClient;
|
import ru.otus.mobile.config.TestContext;
|
||||||
import ru.otus.mobile.driver.MobileSession;
|
import ru.otus.mobile.driver.EmulatorQueue;
|
||||||
import ru.otus.mobile.driver.MobileSessionFactory;
|
import ru.otus.mobile.driver.LogcatCollector;
|
||||||
|
import ru.otus.mobile.driver.MobileDriverFactory;
|
||||||
import ru.otus.mobile.guice.CoreModule;
|
import ru.otus.mobile.guice.CoreModule;
|
||||||
import ru.otus.mobile.page.LoginPage;
|
|
||||||
|
|
||||||
public final class MobileExtension implements
|
public final class MobileExtension implements
|
||||||
BeforeEachCallback,
|
TestInstancePostProcessor,
|
||||||
|
ParameterResolver,
|
||||||
|
AfterTestExecutionCallback,
|
||||||
AfterEachCallback {
|
AfterEachCallback {
|
||||||
private static final ExtensionContext.Namespace NAMESPACE =
|
private static final ExtensionContext.Namespace NAMESPACE =
|
||||||
ExtensionContext.Namespace.create(MobileExtension.class);
|
ExtensionContext.Namespace.create(MobileExtension.class);
|
||||||
private static final String SESSION_KEY = "mobile.session";
|
private static final String SESSION_KEY = "mobile.session";
|
||||||
|
private static final Injector INJECTOR = Guice.createInjector(new CoreModule());
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void beforeEach(ExtensionContext context) {
|
public void postProcessTestInstance(Object testInstance, ExtensionContext context) {
|
||||||
Injector injector = rootInjector(context);
|
INJECTOR.injectMembers(testInstance);
|
||||||
TestAccount account = resolveAccount(context);
|
}
|
||||||
injector.getInstance(DbClient.class).resetData(account);
|
|
||||||
|
|
||||||
MobileSessionFactory sessionFactory = injector.getInstance(MobileSessionFactory.class);
|
@Override
|
||||||
MobileSession session = sessionFactory.create(account, context.getDisplayName());
|
public boolean supportsParameter(ParameterContext parameterContext, ExtensionContext extensionContext)
|
||||||
session.activate();
|
throws ParameterResolutionException {
|
||||||
session.injectMembers(context.getRequiredTestInstance());
|
return TestContext.class.equals(parameterContext.getParameter().getType());
|
||||||
context.getStore(NAMESPACE).put(SESSION_KEY, session);
|
}
|
||||||
injector.getInstance(LoginPage.class).login(account);
|
|
||||||
|
@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
|
@Override
|
||||||
public void afterEach(ExtensionContext context) {
|
public void afterEach(ExtensionContext context) {
|
||||||
MobileSession session = context.getStore(NAMESPACE).remove(SESSION_KEY, MobileSession.class);
|
TestContext testContext = context.getStore(NAMESPACE).remove(SESSION_KEY, TestContext.class);
|
||||||
if (session != null) {
|
if (testContext != null) {
|
||||||
session.close();
|
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) {
|
private TestAccount resolveAccount(ExtensionContext context) {
|
||||||
MobileUser annotation = context.getRequiredTestClass().getAnnotation(MobileUser.class);
|
MobileUser annotation = context.getRequiredTestClass().getAnnotation(MobileUser.class);
|
||||||
if (annotation == null) {
|
if (annotation == null) {
|
||||||
@@ -55,4 +73,30 @@ public final class MobileExtension implements
|
|||||||
}
|
}
|
||||||
return annotation.value();
|
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.Provides;
|
||||||
import com.google.inject.Singleton;
|
import com.google.inject.Singleton;
|
||||||
import ru.otus.mobile.config.MobileConfig;
|
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.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;
|
import java.util.List;
|
||||||
|
|
||||||
public final class CoreModule extends AbstractModule {
|
public final class CoreModule extends AbstractModule {
|
||||||
@Override
|
|
||||||
protected void configure() {
|
|
||||||
}
|
|
||||||
|
|
||||||
@Provides
|
@Provides
|
||||||
@Singleton
|
@Singleton
|
||||||
MobileConfig mobileConfig() {
|
MobileConfig mobileConfig() {
|
||||||
String appPackage = value("app.package", "APP_PACKAGE", "ru.otus.wishlist");
|
String appPackage = value("app.package", "APP_PACKAGE", "ru.otus.wishlist");
|
||||||
String appUrl = value("app.url", "APP_URL", "http://wiremock:8080/wishlist.apk");
|
String appUrl = value("app.url", "APP_URL", "http://wiremock:8080/wishlist.apk");
|
||||||
String reservationOwner = value("reservation.owner", "RESERVATION_OWNER", "user4us");
|
String appiumHost = value("mobile.host", "MOBILE_HOST", "127.0.0.1");
|
||||||
String rawEmulators = value(
|
List<MobileConfig.Emulator> emulators = Arrays.stream(TestEmulator.values())
|
||||||
"mobile.emulators",
|
.map(emulator -> emulator.toEmulator(appiumHost, appUrl))
|
||||||
"MOBILE_EMULATORS",
|
.toList();
|
||||||
"android-emulator-1|http://127.0.0.1:4723|Android Emulator,"
|
return new MobileConfig(appPackage, appUrl, appiumHost, emulators);
|
||||||
+ "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();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Provides
|
@Provides
|
||||||
@@ -48,24 +32,6 @@ public final class CoreModule extends AbstractModule {
|
|||||||
return new DbConfig(url, user, password);
|
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) {
|
private String requiredValue(String property, String env) {
|
||||||
String value = value(property, env, null);
|
String value = value(property, env, null);
|
||||||
if (value == 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;
|
package ru.otus.mobile.page;
|
||||||
|
|
||||||
import ru.otus.mobile.component.BottomNavigationComponent;
|
import ru.otus.mobile.component.BottomNavigationComponent;
|
||||||
import ru.otus.mobile.config.MobileConfig;
|
import ru.otus.mobile.component.TopBarComponent;
|
||||||
|
|
||||||
public abstract class AbsBasePage extends AbsPageObject {
|
public abstract class AbsBasePage extends AbsPageObject {
|
||||||
protected final BottomNavigationComponent bottomNavigation;
|
protected final BottomNavigationComponent bottomNavigation =
|
||||||
private final MobileConfig config;
|
new BottomNavigationComponent(byId("bottom_navigation"));
|
||||||
|
protected final TopBarComponent topBar = new TopBarComponent(byId("top_app_bar"));
|
||||||
protected AbsBasePage(MobileConfig config, BottomNavigationComponent bottomNavigation) {
|
|
||||||
super(config);
|
|
||||||
this.config = config;
|
|
||||||
this.bottomNavigation = bottomNavigation;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected MobileConfig config() {
|
|
||||||
return config;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,103 +1,34 @@
|
|||||||
package ru.otus.mobile.page;
|
package ru.otus.mobile.page;
|
||||||
|
|
||||||
import com.codeborne.selenide.CollectionCondition;
|
|
||||||
import com.codeborne.selenide.Condition;
|
|
||||||
import com.codeborne.selenide.SelenideElement;
|
import com.codeborne.selenide.SelenideElement;
|
||||||
import com.codeborne.selenide.WebDriverRunner;
|
import com.codeborne.selenide.WebDriverRunner;
|
||||||
import com.codeborne.selenide.appium.SelenideAppiumCollection;
|
import com.codeborne.selenide.appium.SelenideAppiumCollection;
|
||||||
import io.appium.java_client.AppiumBy;
|
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.$$;
|
||||||
import static com.codeborne.selenide.appium.SelenideAppium.$;
|
import static com.codeborne.selenide.appium.SelenideAppium.$;
|
||||||
|
|
||||||
public abstract class AbsPageObject {
|
public abstract class AbsPageObject {
|
||||||
private final MobileConfig config;
|
private static final String APP_PACKAGE_PROPERTY = "app.package";
|
||||||
|
private static final String DEFAULT_APP_PACKAGE = "ru.otus.wishlist";
|
||||||
protected AbsPageObject(MobileConfig config) {
|
|
||||||
this.config = config;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected SelenideElement byId(String id) {
|
protected SelenideElement byId(String id) {
|
||||||
return $(AppiumBy.id(fullId(id)));
|
return $(AppiumBy.id(fullIdValue(id)));
|
||||||
}
|
}
|
||||||
|
|
||||||
protected SelenideAppiumCollection allById(String id) {
|
protected SelenideAppiumCollection allById(String id) {
|
||||||
return $$(AppiumBy.id(fullId(id)));
|
return $$(AppiumBy.id(fullIdValue(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));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void back() {
|
protected void back() {
|
||||||
WebDriverRunner.getWebDriver().navigate().back();
|
WebDriverRunner.getWebDriver().navigate().back();
|
||||||
}
|
}
|
||||||
|
|
||||||
protected String xpathLiteral(String value) {
|
protected static String fullIdValue(String id) {
|
||||||
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) {
|
|
||||||
if (id.contains(":")) {
|
if (id.contains(":")) {
|
||||||
return id;
|
return id;
|
||||||
}
|
}
|
||||||
return config.appPackage() + ":id/" + id;
|
String appPackage = System.getProperty(APP_PACKAGE_PROPERTY, DEFAULT_APP_PACKAGE);
|
||||||
}
|
return appPackage + ":id/" + id;
|
||||||
|
|
||||||
private String escape(String value) {
|
|
||||||
return value.replace("\"", "\\\"");
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,36 +1,36 @@
|
|||||||
package ru.otus.mobile.page;
|
package ru.otus.mobile.page;
|
||||||
|
|
||||||
import com.codeborne.selenide.Condition;
|
import com.codeborne.selenide.Condition;
|
||||||
import com.google.inject.Inject;
|
import com.codeborne.selenide.SelenideElement;
|
||||||
import ru.otus.mobile.component.BottomNavigationComponent;
|
import com.google.inject.Singleton;
|
||||||
import ru.otus.mobile.component.GiftFormComponent;
|
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 {
|
public final class GiftsPage extends AbsBasePage {
|
||||||
private final GiftFormComponent form;
|
private final SelenideElement addButton = byId("add_button");
|
||||||
|
private final GiftsContentComponent content = new GiftsContentComponent(byId("gifts_content"));
|
||||||
@Inject
|
private final GiftFormComponent form = new GiftFormComponent(byId("gift_edit_bottom_sheet"));
|
||||||
public GiftsPage(MobileConfig config, BottomNavigationComponent bottomNavigation, GiftFormComponent form) {
|
|
||||||
super(config, bottomNavigation);
|
|
||||||
this.form = form;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void createGift(String name) {
|
public void createGift(String name) {
|
||||||
tap("add_button");
|
addButton.click();
|
||||||
form.save(name, "100");
|
form.save(name, "100");
|
||||||
|
content.shouldBe(Condition.visible);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void editGift(String oldName, String newName) {
|
public void editGift(String oldName, String newName) {
|
||||||
openGift(oldName);
|
openGift(oldName);
|
||||||
tap("edit_button");
|
content.byTitle(oldName).edit();
|
||||||
form.save(newName, "100");
|
form.save(newName, "100");
|
||||||
|
content.shouldBe(Condition.visible);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void shouldSeeGift(String name) {
|
public void shouldSeeGift(String name) {
|
||||||
scrollToText(name).shouldBe(Condition.visible);
|
content.shouldBe(Condition.visible);
|
||||||
|
content.byTitle(name).shouldHaveTitle(name);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void openGift(String name) {
|
private void openGift(String name) {
|
||||||
scrollToText(name).click();
|
content.byTitle(name).open();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,30 +1,33 @@
|
|||||||
package ru.otus.mobile.page;
|
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.component.AlertDialogComponent;
|
||||||
import ru.otus.mobile.config.MobileConfig;
|
|
||||||
import ru.otus.mobile.config.TestAccount;
|
import ru.otus.mobile.config.TestAccount;
|
||||||
|
|
||||||
|
@Singleton
|
||||||
public final class LoginPage extends AbsPageObject {
|
public final class LoginPage extends AbsPageObject {
|
||||||
private final AlertDialogComponent alertDialog;
|
private final SelenideElement usernameInput = byId("username_text_input");
|
||||||
|
private final SelenideElement passwordInput = byId("password_text_input");
|
||||||
@Inject
|
private final SelenideElement loginButton = byId("log_in_button");
|
||||||
public LoginPage(MobileConfig config, AlertDialogComponent alertDialog) {
|
private final SelenideElement appMainContainer = byId("app_main_fragment_container");
|
||||||
super(config);
|
private final AlertDialogComponent alertDialog = new AlertDialogComponent(appMainContainer);
|
||||||
this.alertDialog = alertDialog;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void login(TestAccount account) {
|
public void login(TestAccount account) {
|
||||||
if (!isOpened()) {
|
if (!isOpened()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
type("username_text_input", account.username());
|
usernameInput.click();
|
||||||
type("password_text_input", account.password());
|
usernameInput.clear();
|
||||||
tap("log_in_button");
|
usernameInput.sendKeys(account.username());
|
||||||
|
passwordInput.click();
|
||||||
|
passwordInput.clear();
|
||||||
|
passwordInput.sendKeys(account.password());
|
||||||
|
loginButton.click();
|
||||||
alertDialog.acceptIfVisible();
|
alertDialog.acceptIfVisible();
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean isOpened() {
|
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;
|
package ru.otus.mobile.page;
|
||||||
|
|
||||||
import com.codeborne.selenide.Condition;
|
import com.codeborne.selenide.Condition;
|
||||||
import com.google.inject.Inject;
|
import com.google.inject.Singleton;
|
||||||
import ru.otus.mobile.component.BottomNavigationComponent;
|
import ru.otus.mobile.component.GiftItemComponent;
|
||||||
import ru.otus.mobile.config.MobileConfig;
|
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 {
|
public final class UsersPage extends AbsBasePage {
|
||||||
@Inject
|
private final UsersContentComponent usersContent = new UsersContentComponent(byId("users_content"));
|
||||||
public UsersPage(MobileConfig config, BottomNavigationComponent bottomNavigation) {
|
private final UsersFilterComponent usersFilter = new UsersFilterComponent(byId("users_filter_bottom_sheet"));
|
||||||
super(config, bottomNavigation);
|
private final WishlistsContentComponent wishlistsContent = new WishlistsContentComponent(byId("wishlists_content"));
|
||||||
}
|
private final GiftsContentComponent giftsContent = new GiftsContentComponent(byId("gifts_content"));
|
||||||
|
private GiftItemComponent currentGift;
|
||||||
|
|
||||||
public void open() {
|
public void open() {
|
||||||
bottomNavigation.openUsers();
|
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) {
|
public void openUser(String username) {
|
||||||
String appPackage = config().appPackage();
|
usersContent.byUsername(username).open();
|
||||||
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();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void openFirstWishlist() {
|
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() {
|
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() {
|
public boolean isReserved() {
|
||||||
return Boolean.parseBoolean(byId("reserved").shouldBe(Condition.visible).getAttribute("checked"));
|
return selectedGift().isReserved();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void toggleReservation() {
|
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;
|
package ru.otus.mobile.page;
|
||||||
|
|
||||||
import com.codeborne.selenide.Condition;
|
import com.codeborne.selenide.Condition;
|
||||||
import com.google.inject.Inject;
|
import com.codeborne.selenide.SelenideElement;
|
||||||
import ru.otus.mobile.component.BottomNavigationComponent;
|
import com.google.inject.Singleton;
|
||||||
|
import ru.otus.mobile.component.WishlistsContentComponent;
|
||||||
import ru.otus.mobile.component.WishlistFormComponent;
|
import ru.otus.mobile.component.WishlistFormComponent;
|
||||||
import ru.otus.mobile.config.MobileConfig;
|
|
||||||
|
|
||||||
|
import java.time.Duration;
|
||||||
|
|
||||||
|
@Singleton
|
||||||
public final class WishlistsPage extends AbsBasePage {
|
public final class WishlistsPage extends AbsBasePage {
|
||||||
private final WishlistFormComponent form;
|
private final SelenideElement addButton = byId("add_button");
|
||||||
|
private final WishlistsContentComponent content = new WishlistsContentComponent(byId("wishlists_content"));
|
||||||
@Inject
|
private final WishlistFormComponent form = new WishlistFormComponent(byId("wishlist_edit_bottom_sheet"));
|
||||||
public WishlistsPage(MobileConfig config, BottomNavigationComponent bottomNavigation, WishlistFormComponent form) {
|
|
||||||
super(config, bottomNavigation);
|
|
||||||
this.form = form;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void open() {
|
public void open() {
|
||||||
bottomNavigation.openWishlists();
|
bottomNavigation.openWishlists();
|
||||||
byId("wishlists_content").shouldBe(Condition.visible);
|
content.shouldBe(Condition.visible, Duration.ofSeconds(15));
|
||||||
}
|
}
|
||||||
|
|
||||||
public void createWishlist(String name) {
|
public void createWishlist(String name) {
|
||||||
tap("add_button");
|
addButton.click();
|
||||||
form.save(name);
|
form.save(name);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void editWishlist(String oldName, String newName) {
|
public void editWishlist(String oldName, String newName) {
|
||||||
ensureWishlistsList();
|
content.byTitle(oldName).edit();
|
||||||
editButtonFor(oldName).shouldBe(Condition.visible).click();
|
|
||||||
form.save(newName);
|
form.save(newName);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void shouldSeeWishlist(String name) {
|
public void shouldSeeWishlist(String name) {
|
||||||
scrollToText(name).shouldBe(Condition.visible);
|
content.byTitle(name).shouldHaveTitle(name);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void openWishlist(String name) {
|
public void openWishlist(String name) {
|
||||||
scrollToText(name).click();
|
content.byTitle(name).open();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void ensureWishlistsList() {
|
public void openFirstWishlist() {
|
||||||
if (exists("wishlist_item") || exists("wishlists")) {
|
content.first().open();
|
||||||
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);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,8 +6,11 @@ import org.junit.jupiter.api.Test;
|
|||||||
import org.junit.jupiter.api.extension.ExtendWith;
|
import org.junit.jupiter.api.extension.ExtendWith;
|
||||||
import ru.otus.mobile.annotations.MobileUser;
|
import ru.otus.mobile.annotations.MobileUser;
|
||||||
import ru.otus.mobile.config.TestAccount;
|
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.extensions.MobileExtension;
|
||||||
import ru.otus.mobile.page.GiftsPage;
|
import ru.otus.mobile.page.GiftsPage;
|
||||||
|
import ru.otus.mobile.page.LoginPage;
|
||||||
import ru.otus.mobile.page.WishlistsPage;
|
import ru.otus.mobile.page.WishlistsPage;
|
||||||
import ru.otus.mobile.util.TestData;
|
import ru.otus.mobile.util.TestData;
|
||||||
|
|
||||||
@@ -23,16 +26,23 @@ public class GiftsTest {
|
|||||||
@Inject
|
@Inject
|
||||||
private TestData testData;
|
private TestData testData;
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
private DbClient dbClient;
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
private LoginPage loginPage;
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@DisplayName("Создание и редактирование подарка")
|
@DisplayName("Создание и редактирование подарка")
|
||||||
void createAndEditGift() {
|
void createAndEditGift(TestContext context) {
|
||||||
String wishlistName = testData.uniqueName("Wishlist");
|
context.account().dataPreparation().accept(dbClient, context.account().username());
|
||||||
|
loginPage.login(context.account());
|
||||||
|
|
||||||
String name = testData.uniqueName("Gift");
|
String name = testData.uniqueName("Gift");
|
||||||
String updated = name + " updated";
|
String updated = name + " updated";
|
||||||
|
|
||||||
wishlistsPage.open();
|
wishlistsPage.open();
|
||||||
wishlistsPage.createWishlist(wishlistName);
|
wishlistsPage.openFirstWishlist();
|
||||||
wishlistsPage.openWishlist(wishlistName);
|
|
||||||
giftsPage.createGift(name);
|
giftsPage.createGift(name);
|
||||||
giftsPage.shouldSeeGift(name);
|
giftsPage.shouldSeeGift(name);
|
||||||
giftsPage.editGift(name, updated);
|
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.Test;
|
||||||
import org.junit.jupiter.api.extension.ExtendWith;
|
import org.junit.jupiter.api.extension.ExtendWith;
|
||||||
import ru.otus.mobile.annotations.MobileUser;
|
import ru.otus.mobile.annotations.MobileUser;
|
||||||
import ru.otus.mobile.config.MobileConfig;
|
|
||||||
import ru.otus.mobile.config.TestAccount;
|
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.extensions.MobileExtension;
|
||||||
|
import ru.otus.mobile.page.LoginPage;
|
||||||
import ru.otus.mobile.page.UsersPage;
|
import ru.otus.mobile.page.UsersPage;
|
||||||
|
|
||||||
import static org.junit.jupiter.api.Assertions.assertNotEquals;
|
import static org.junit.jupiter.api.Assertions.assertNotEquals;
|
||||||
@@ -19,13 +21,20 @@ public class ReservationTest {
|
|||||||
private UsersPage usersPage;
|
private UsersPage usersPage;
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
private MobileConfig mobileConfig;
|
private DbClient dbClient;
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
private LoginPage loginPage;
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@DisplayName("Изменение статуса резервирования подарка другого пользователя")
|
@DisplayName("Изменение статуса резервирования подарка другого пользователя")
|
||||||
void changeReservationStatus() {
|
void changeReservationStatus(TestContext context) {
|
||||||
|
TestAccount.RESERVATION_OWNER.dataPreparation().accept(dbClient, TestAccount.RESERVATION_OWNER.username());
|
||||||
|
loginPage.login(context.account());
|
||||||
|
|
||||||
usersPage.open();
|
usersPage.open();
|
||||||
usersPage.openUser(mobileConfig.reservationOwnerUsername());
|
usersPage.filterByUsername(TestAccount.RESERVATION_OWNER.username());
|
||||||
|
usersPage.openUser(TestAccount.RESERVATION_OWNER.username());
|
||||||
usersPage.openFirstWishlist();
|
usersPage.openFirstWishlist();
|
||||||
usersPage.openFirstGift();
|
usersPage.openFirstGift();
|
||||||
boolean before = usersPage.isReserved();
|
boolean before = usersPage.isReserved();
|
||||||
|
|||||||
@@ -6,7 +6,10 @@ import org.junit.jupiter.api.Test;
|
|||||||
import org.junit.jupiter.api.extension.ExtendWith;
|
import org.junit.jupiter.api.extension.ExtendWith;
|
||||||
import ru.otus.mobile.annotations.MobileUser;
|
import ru.otus.mobile.annotations.MobileUser;
|
||||||
import ru.otus.mobile.config.TestAccount;
|
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.extensions.MobileExtension;
|
||||||
|
import ru.otus.mobile.page.LoginPage;
|
||||||
import ru.otus.mobile.page.WishlistsPage;
|
import ru.otus.mobile.page.WishlistsPage;
|
||||||
import ru.otus.mobile.util.TestData;
|
import ru.otus.mobile.util.TestData;
|
||||||
|
|
||||||
@@ -19,9 +22,18 @@ public class WishlistsTest {
|
|||||||
@Inject
|
@Inject
|
||||||
private TestData testData;
|
private TestData testData;
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
private DbClient dbClient;
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
private LoginPage loginPage;
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@DisplayName("Создание и редактирование списка желаний")
|
@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 name = testData.uniqueName("Wishlist");
|
||||||
String updated = name + " updated";
|
String updated = name + " updated";
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user