1
0
forked from BRT/arc

Update Ozon screen dump checklist for version 19.19.1: add versioning details, revise instructions, and integrate Compose-specific parsing notes. Include new capture script for automated screenshot and UI dump generation.

This commit is contained in:
slava 2026-06-06 18:48:17 +05:00
parent ce3bc99ca8
commit ba476bae50
3 changed files with 184 additions and 43 deletions

View File

@ -285,13 +285,50 @@ void GetCardInfoScript::doStart() {
continue;
}
AdbUtils::makeTap(m_deviceId, cardBtn.x(), cardBtn.y());
// Открываем экран нужной карты.
if (xmlScreenParser.versionTrack() >= AppVersionTrack::V19_19_1) {
// Новый UI: тап по НИЖНЕЙ (чиповой) части тайла "Основной счёт" открывает экран
// счёта с РАЗДЕЛЬНЫМИ кнопками карт ("Bank Card NNNN"). Тапаем кнопку нужной
// карты — открывается именно она. (Тап в центр тайла открывал бы переднюю карту
// карусели, и для всех материалов читался номер одной карты.)
const int accY = cardBtn.y2() - cardBtn.height() / 5; // зона чипов карт внизу тайла
qDebug() << "[GetCardInfo] Opening account screen via tile bottom at" << cardBtn.x() << accY;
AdbUtils::makeTap(m_deviceId, cardBtn.x(), accY);
// Скроллим WebView вверх, пока "Показать" не окажется в верхней трети экрана.
// На устройствах с короткой высотой (например, SM-A165F, h=2340) кнопка "Показать"
// находится за bottom navigation, и тап уходит в нав-бар.
const int targetTopY = device.screenHeight / 3;
for (int scrollAttempt = 0; scrollAttempt < 6; ++scrollAttempt) {
Node cardTile;
for (int a = 0; a < 8; ++a) {
QThread::msleep(a == 0 ? 1500 : 700);
xmlScreenParser.parseAndSaveXml(AdbUtils::getScreenDumpAsXml(m_deviceId, ++m_counter));
closeGooglePlayBannerIfVisible(m_deviceId, device.screenWidth, device.screenHeight);
cardTile = xmlScreenParser.findAccountCardButton(card.lastNumbers);
if (!cardTile.isEmpty()) break;
qDebug() << "[GetCardInfo] Waiting account card button" << card.lastNumbers
<< "attempt" << a + 1 << "/ 8";
}
if (!cardTile.isEmpty()) {
qDebug() << "[GetCardInfo] Tapping account card button" << card.lastNumbers
<< "at" << cardTile.x() << cardTile.y();
AdbUtils::makeTap(m_deviceId, cardTile.x(), cardTile.y());
} else {
qWarning() << "[GetCardInfo] Account card button not found for" << card.lastNumbers
<< "— fallback to tile center tap";
AdbUtils::makeTap(m_deviceId, cardBtn.x(), cardBtn.y());
}
} else {
// Старый UI: тап по тайлу открывает карту напрямую.
AdbUtils::makeTap(m_deviceId, cardBtn.x(), cardBtn.y());
}
// Подводим "Показать" в зону тапа [minTapY, midY]: ниже статус-бара/тулбара и
// ВЫШЕ середины экрана (после раскрытия номер/срок/CVV появляются ниже кнопки —
// ей нужно место снизу). Свайпы делаем МЕДЛЕННЫМИ (durationMs), иначе быстрый
// ф_линг перелистывает "Показать" под самый верх (y≈3, под статус-бар) и тап мимо.
// - cy > midY → кнопка ниже середины (или за нижней навигацией): листаем вверх.
// - cy < minTapY → перелистнули под статус-бар: листаем обратно вниз.
const int minTapY = device.screenHeight / 8;
const int midY = device.screenHeight / 2;
const int swipeMs = 700;
for (int scrollAttempt = 0; scrollAttempt < 8; ++scrollAttempt) {
QThread::msleep(scrollAttempt == 0 ? 2000 : 600);
xmlScreenParser.parseAndSaveXml(AdbUtils::getScreenDumpAsXml(m_deviceId, ++m_counter));
closeGooglePlayBannerIfVisible(m_deviceId, device.screenWidth, device.screenHeight);
@ -307,15 +344,24 @@ void GetCardInfoScript::doStart() {
}
continue;
}
if (candidate.y() <= targetTopY) {
qDebug() << "[GetCardInfo] 'Показать' already at top y=" << candidate.y();
const int cy = candidate.y();
if (cy >= minTapY && cy <= midY) {
qDebug() << "[GetCardInfo] 'Показать' in tap zone (above middle) y=" << cy;
break;
}
const int swipeFromY = candidate.y();
const int swipeToY = device.screenHeight / 6;
qDebug() << "[GetCardInfo] Scrolling 'Показать' up:" << swipeFromY << "->" << swipeToY;
AdbUtils::makeSwipe(m_deviceId, device.screenWidth / 2, swipeFromY,
device.screenWidth / 2, swipeToY);
if (cy > midY) {
// Ниже середины — листаем вверх, целясь в верхнюю треть (медленно, без перелёта).
const int swipeToY = device.screenHeight / 3;
qDebug() << "[GetCardInfo] Scrolling 'Показать' up:" << cy << "->" << swipeToY;
AdbUtils::makeSwipe(m_deviceId, device.screenWidth / 2, cy,
device.screenWidth / 2, swipeToY, swipeMs);
} else {
// Перелистнули под статус-бар (y≈3) — листаем обратно вниз в верхнюю треть.
const int swipeToY = device.screenHeight / 3;
qDebug() << "[GetCardInfo] 'Показать' overshot to y=" << cy << ", scrolling down to" << swipeToY;
AdbUtils::makeSwipe(m_deviceId, device.screenWidth / 2, device.screenHeight / 8,
device.screenWidth / 2, swipeToY, swipeMs);
}
}
// Ждём экран карты и стабильной геометрии блока реквизитов.
@ -422,7 +468,16 @@ void GetCardInfoScript::doStart() {
}
}
if (!cardNumber.isEmpty()) {
if (!cardNumber.isEmpty() && !cardNumber.endsWith(card.lastNumbers)) {
// Прочитан номер ЧУЖОЙ карты (карусель отдала не ту) — НЕ сохраняем, иначе
// материал получит неверный номер. Раньше из-за этого все карты получали
// номер передней карты карусели.
const QString err = "Card number " + cardNumber + " does not end with target "
+ card.lastNumbers + " — skipping save";
qWarning() << "[GetCardInfo]" << err;
AppLogger::log("ozon/GetCardInfo", m_deviceId, err,
AdbUtils::captureScreenshotBytes(m_deviceId), "FETCH_PROFILE");
} else if (!cardNumber.isEmpty()) {
qDebug() << "[GetCardInfo] Card" << card.lastNumbers << "full number:" << cardNumber;
// Апсёртим запись только теперь — с полным номером карты.
MaterialInfo toSave = card;
@ -443,12 +498,16 @@ void GetCardInfoScript::doStart() {
"FETCH_PROFILE");
}
// Возвращаемся на домашний экран для следующей карты
// Возвращаемся на домашний экран для следующей карты. В новом UI путь длиннее
// (карта → "Основной счёт" → главная), поэтому жмём "назад" до главной (макс 3).
if (i < cards.size() - 1) {
AdbUtils::goBack(m_deviceId);
QThread::msleep(2000);
xmlScreenParser.parseAndSaveXml(AdbUtils::getScreenDumpAsXml(m_deviceId, ++m_counter));
closeGooglePlayBannerIfVisible(m_deviceId, device.screenWidth, device.screenHeight);
for (int b = 0; b < 3; ++b) {
AdbUtils::goBack(m_deviceId);
QThread::msleep(1500);
xmlScreenParser.parseAndSaveXml(AdbUtils::getScreenDumpAsXml(m_deviceId, ++m_counter));
closeGooglePlayBannerIfVisible(m_deviceId, device.screenWidth, device.screenHeight);
if (xmlScreenParser.isHomeScreen()) break;
}
}
}

View File

@ -454,7 +454,13 @@ Node ScreenXmlParser::findCardButtonByLastNumbers(const QString &lastNumbers) {
for (const Node &node : m_nodes) {
if (node.className != "android.widget.Button" || !node.clickable) continue;
const QString t = node.text.trimmed();
if (!t.endsWith(lastNumbers)) continue;
// Старый UI: отдельная кнопка карты оканчивается на 4 цифры.
// Compose-UI (>= 19.x): обе карты в одной плитке "… Bank Card 7919 Bank Card 9865",
// поэтому endsWith находит только последнюю — ищем ещё "Bank Card NNNN"/"Карта NNNN".
const bool matches = t.endsWith(lastNumbers)
|| t.contains("Bank Card " + lastNumbers)
|| t.contains(QString::fromUtf8("Карта ") + lastNumbers);
if (!matches) continue;
// Отсекаем строки из ленты переводов (см. parseCardsOnHomeScreen)
if (t.contains(QString::fromUtf8("Переводы"))
|| t.contains(QStringLiteral("**"))
@ -476,6 +482,40 @@ Node ScreenXmlParser::findCardButtonByLastNumbers(const QString &lastNumbers) {
return {};
}
QString ScreenXmlParser::parseActiveCardLast4() {
// Маска реквизитов активной карты: "•••• •••• •••• 9865". Отличаем от плиток
// карусели ("…•• 9865") по наличию группы из 4 точек "••••".
static const QRegularExpression tail(R"((\d{4})\s*$)");
for (const Node &node : m_nodes) {
const QString t = node.text.trimmed();
if (!t.contains(QString::fromUtf8("••••"))) continue;
if (const auto m = tail.match(t); m.hasMatch())
return m.captured(1);
}
return {};
}
Node ScreenXmlParser::findCardCarouselTile(const QString &lastNumbers) {
// Плитка карты на экране "Карта": Button "Bank Card Виртуальная карта •• NNNN".
for (const Node &node : m_nodes) {
if (node.className != "android.widget.Button" || !node.clickable) continue;
const QString t = node.text.trimmed();
if (t.contains(QString::fromUtf8("Виртуальная карта")) && t.endsWith(lastNumbers))
return node;
}
return {};
}
Node ScreenXmlParser::findAccountCardButton(const QString &lastNumbers) {
const QString needle = "Bank Card " + lastNumbers;
for (const Node &node : m_nodes) {
if (!node.clickable) continue;
if (node.text.trimmed() == needle || node.contentDesc.trimmed() == needle)
return node;
}
return {};
}
Node ScreenXmlParser::findMainAccountButton() {
for (const Node &node : m_nodes) {
if (node.className != "android.widget.Button") continue;
@ -549,34 +589,63 @@ QList<MaterialInfo> ScreenXmlParser::parseCardsOnHomeScreen() {
if (commonAmount == 0.0)
qWarning() << "[parseCards] Balance button not found or amount=0";
// 2. Собираем все кнопки карт (текст заканчивается на 4 цифры).
// Записи из ленты переводов выглядят как кнопки и тоже заканчиваются на 4 цифры
// ("Евгений Дмитриевич Р. 100 ₽ Переводы • Карта **1092") — отсеиваем по маркерам.
QList<MaterialInfo> cards;
for (const Node &node : m_nodes) {
if (node.className != "android.widget.Button" || !node.clickable) continue;
QString t = node.text.trimmed();
if (!endsWithFourDigits.match(t).hasMatch()) continue;
// Маркеры записи из ленты переводов:
// - слово "Переводы"
// - маска "**" перед цифрами
// - знак суммы транзакции (минус U+2212 или плюс)
if (t.contains(QString::fromUtf8("Переводы"))
// Маркеры записи из ленты переводов (не карта):
// - слово "Переводы", маска "**" перед цифрами, знак суммы (минус U+2212 или плюс).
auto isTransferFeedRow = [](const QString &t) {
return t.contains(QString::fromUtf8("Переводы"))
|| t.contains(QStringLiteral("**"))
|| t.contains(QChar(0x2212)) || t.contains(QChar('+'))) {
|| t.contains(QChar(0x2212)) || t.contains(QChar('+'));
};
// 2. Собираем номера карт.
// В Compose-UI Ozon (>= 19.x) ОБЕ карты лежат в тексте балансовой кнопки одной
// строкой: "Основной счёт … ₽ Bank Card 7919 Bank Card 9865". Вытаскиваем ВСЕ
// вхождения "Bank Card NNNN" / "Карта NNNN" из текста кнопок (а не только t.right(4),
// который терял все карты кроме последней).
QList<MaterialInfo> cards;
QStringList seen;
static const QRegularExpression bankCardRx(R"((?:Bank Card|Карта)\s+(\d{4}))");
for (const Node &node : m_nodes) {
if (node.className != "android.widget.Button") continue;
const QString t = node.text;
if (isTransferFeedRow(t)) {
qDebug() << "[parseCards] Skipping transfer-feed row:" << t;
continue;
}
auto it = bankCardRx.globalMatch(t);
while (it.hasNext()) {
const QString ln = it.next().captured(1);
if (seen.contains(ln)) continue;
seen.append(ln);
MaterialInfo acc;
acc.lastNumbers = ln;
acc.amount = commonAmount;
acc.currency = "RUB";
acc.description = "Виртуальная карта";
cards.append(acc);
qDebug() << "[parseCards] Card found (Bank Card):" << ln << "from:" << t;
}
}
MaterialInfo acc;
acc.lastNumbers = t.right(4);
acc.amount = commonAmount;
acc.currency = "RUB";
acc.description = "Виртуальная карта";
qDebug() << "[parseCards] Card found: lastNumbers=" << acc.lastNumbers
<< "text=" << node.text << "amount=" << commonAmount;
cards.append(acc);
// 3. Fallback (старый UI): отдельные кнопки, текст которых оканчивается 4 цифрами.
if (cards.isEmpty()) {
for (const Node &node : m_nodes) {
if (node.className != "android.widget.Button" || !node.clickable) continue;
const QString t = node.text.trimmed();
if (!endsWithFourDigits.match(t).hasMatch()) continue;
if (isTransferFeedRow(t)) {
qDebug() << "[parseCards] Skipping transfer-feed row:" << t;
continue;
}
MaterialInfo acc;
acc.lastNumbers = t.right(4);
acc.amount = commonAmount;
acc.currency = "RUB";
acc.description = "Виртуальная карта";
qDebug() << "[parseCards] Card found (fallback):" << acc.lastNumbers
<< "text=" << node.text << "amount=" << commonAmount;
cards.append(acc);
}
}
qDebug() << "[parseCards] Total cards found:" << cards.size();

View File

@ -73,6 +73,19 @@ public:
// Finds a card button whose text ends with the given last 4 digits
Node findCardButtonByLastNumbers(const QString &lastNumbers);
// Экран "Карта": last4 активной карты из маски "•••• •••• •••• NNNN".
// Пусто, если маскированный номер не найден.
QString parseActiveCardLast4();
// Экран "Карта": плитка карты в карусели по последним 4 цифрам
// ("Bank Card Виртуальная карта •• NNNN"). Тап по ней делает карту активной.
Node findCardCarouselTile(const QString &lastNumbers);
// Экран "Основной счёт": отдельная кнопка карты "Bank Card NNNN" (text или
// content-desc). Тап по ней открывает экран именно этой карты (а не передней
// карты карусели). NNNN — последние 4 цифры.
Node findAccountCardButton(const QString &lastNumbers);
// Returns all cards visible on the home screen as MaterialInfo stubs
// (lastNumbers, description, amount filled; id=-1, cardNumber empty)
QList<MaterialInfo> parseCardsOnHomeScreen();