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:
parent
ce3bc99ca8
commit
ba476bae50
@ -285,13 +285,50 @@ void GetCardInfoScript::doStart() {
|
|||||||
continue;
|
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 вверх, пока "Показать" не окажется в верхней трети экрана.
|
Node cardTile;
|
||||||
// На устройствах с короткой высотой (например, SM-A165F, h=2340) кнопка "Показать"
|
for (int a = 0; a < 8; ++a) {
|
||||||
// находится за bottom navigation, и тап уходит в нав-бар.
|
QThread::msleep(a == 0 ? 1500 : 700);
|
||||||
const int targetTopY = device.screenHeight / 3;
|
xmlScreenParser.parseAndSaveXml(AdbUtils::getScreenDumpAsXml(m_deviceId, ++m_counter));
|
||||||
for (int scrollAttempt = 0; scrollAttempt < 6; ++scrollAttempt) {
|
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);
|
QThread::msleep(scrollAttempt == 0 ? 2000 : 600);
|
||||||
xmlScreenParser.parseAndSaveXml(AdbUtils::getScreenDumpAsXml(m_deviceId, ++m_counter));
|
xmlScreenParser.parseAndSaveXml(AdbUtils::getScreenDumpAsXml(m_deviceId, ++m_counter));
|
||||||
closeGooglePlayBannerIfVisible(m_deviceId, device.screenWidth, device.screenHeight);
|
closeGooglePlayBannerIfVisible(m_deviceId, device.screenWidth, device.screenHeight);
|
||||||
@ -307,15 +344,24 @@ void GetCardInfoScript::doStart() {
|
|||||||
}
|
}
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if (candidate.y() <= targetTopY) {
|
const int cy = candidate.y();
|
||||||
qDebug() << "[GetCardInfo] 'Показать' already at top y=" << candidate.y();
|
if (cy >= minTapY && cy <= midY) {
|
||||||
|
qDebug() << "[GetCardInfo] 'Показать' in tap zone (above middle) y=" << cy;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
const int swipeFromY = candidate.y();
|
if (cy > midY) {
|
||||||
const int swipeToY = device.screenHeight / 6;
|
// Ниже середины — листаем вверх, целясь в верхнюю треть (медленно, без перелёта).
|
||||||
qDebug() << "[GetCardInfo] Scrolling 'Показать' up:" << swipeFromY << "->" << swipeToY;
|
const int swipeToY = device.screenHeight / 3;
|
||||||
AdbUtils::makeSwipe(m_deviceId, device.screenWidth / 2, swipeFromY,
|
qDebug() << "[GetCardInfo] Scrolling 'Показать' up:" << cy << "->" << swipeToY;
|
||||||
device.screenWidth / 2, 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;
|
qDebug() << "[GetCardInfo] Card" << card.lastNumbers << "full number:" << cardNumber;
|
||||||
// Апсёртим запись только теперь — с полным номером карты.
|
// Апсёртим запись только теперь — с полным номером карты.
|
||||||
MaterialInfo toSave = card;
|
MaterialInfo toSave = card;
|
||||||
@ -443,12 +498,16 @@ void GetCardInfoScript::doStart() {
|
|||||||
"FETCH_PROFILE");
|
"FETCH_PROFILE");
|
||||||
}
|
}
|
||||||
|
|
||||||
// Возвращаемся на домашний экран для следующей карты
|
// Возвращаемся на домашний экран для следующей карты. В новом UI путь длиннее
|
||||||
|
// (карта → "Основной счёт" → главная), поэтому жмём "назад" до главной (макс 3).
|
||||||
if (i < cards.size() - 1) {
|
if (i < cards.size() - 1) {
|
||||||
AdbUtils::goBack(m_deviceId);
|
for (int b = 0; b < 3; ++b) {
|
||||||
QThread::msleep(2000);
|
AdbUtils::goBack(m_deviceId);
|
||||||
xmlScreenParser.parseAndSaveXml(AdbUtils::getScreenDumpAsXml(m_deviceId, ++m_counter));
|
QThread::msleep(1500);
|
||||||
closeGooglePlayBannerIfVisible(m_deviceId, device.screenWidth, device.screenHeight);
|
xmlScreenParser.parseAndSaveXml(AdbUtils::getScreenDumpAsXml(m_deviceId, ++m_counter));
|
||||||
|
closeGooglePlayBannerIfVisible(m_deviceId, device.screenWidth, device.screenHeight);
|
||||||
|
if (xmlScreenParser.isHomeScreen()) break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -454,7 +454,13 @@ Node ScreenXmlParser::findCardButtonByLastNumbers(const QString &lastNumbers) {
|
|||||||
for (const Node &node : m_nodes) {
|
for (const Node &node : m_nodes) {
|
||||||
if (node.className != "android.widget.Button" || !node.clickable) continue;
|
if (node.className != "android.widget.Button" || !node.clickable) continue;
|
||||||
const QString t = node.text.trimmed();
|
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)
|
// Отсекаем строки из ленты переводов (см. parseCardsOnHomeScreen)
|
||||||
if (t.contains(QString::fromUtf8("Переводы"))
|
if (t.contains(QString::fromUtf8("Переводы"))
|
||||||
|| t.contains(QStringLiteral("**"))
|
|| t.contains(QStringLiteral("**"))
|
||||||
@ -476,6 +482,40 @@ Node ScreenXmlParser::findCardButtonByLastNumbers(const QString &lastNumbers) {
|
|||||||
return {};
|
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() {
|
Node ScreenXmlParser::findMainAccountButton() {
|
||||||
for (const Node &node : m_nodes) {
|
for (const Node &node : m_nodes) {
|
||||||
if (node.className != "android.widget.Button") continue;
|
if (node.className != "android.widget.Button") continue;
|
||||||
@ -549,34 +589,63 @@ QList<MaterialInfo> ScreenXmlParser::parseCardsOnHomeScreen() {
|
|||||||
if (commonAmount == 0.0)
|
if (commonAmount == 0.0)
|
||||||
qWarning() << "[parseCards] Balance button not found or amount=0";
|
qWarning() << "[parseCards] Balance button not found or amount=0";
|
||||||
|
|
||||||
// 2. Собираем все кнопки карт (текст заканчивается на 4 цифры).
|
// Маркеры записи из ленты переводов (не карта):
|
||||||
// Записи из ленты переводов выглядят как кнопки и тоже заканчиваются на 4 цифры
|
// - слово "Переводы", маска "**" перед цифрами, знак суммы (минус U+2212 или плюс).
|
||||||
// ("Евгений Дмитриевич Р. − 100 ₽ Переводы • Карта **1092") — отсеиваем по маркерам.
|
auto isTransferFeedRow = [](const QString &t) {
|
||||||
QList<MaterialInfo> cards;
|
return t.contains(QString::fromUtf8("Переводы"))
|
||||||
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("Переводы"))
|
|
||||||
|| t.contains(QStringLiteral("**"))
|
|| 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;
|
qDebug() << "[parseCards] Skipping transfer-feed row:" << t;
|
||||||
continue;
|
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;
|
// 3. Fallback (старый UI): отдельные кнопки, текст которых оканчивается 4 цифрами.
|
||||||
acc.lastNumbers = t.right(4);
|
if (cards.isEmpty()) {
|
||||||
acc.amount = commonAmount;
|
for (const Node &node : m_nodes) {
|
||||||
acc.currency = "RUB";
|
if (node.className != "android.widget.Button" || !node.clickable) continue;
|
||||||
acc.description = "Виртуальная карта";
|
const QString t = node.text.trimmed();
|
||||||
qDebug() << "[parseCards] Card found: lastNumbers=" << acc.lastNumbers
|
if (!endsWithFourDigits.match(t).hasMatch()) continue;
|
||||||
<< "text=" << node.text << "amount=" << commonAmount;
|
if (isTransferFeedRow(t)) {
|
||||||
cards.append(acc);
|
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();
|
qDebug() << "[parseCards] Total cards found:" << cards.size();
|
||||||
|
|||||||
@ -73,6 +73,19 @@ public:
|
|||||||
// Finds a card button whose text ends with the given last 4 digits
|
// Finds a card button whose text ends with the given last 4 digits
|
||||||
Node findCardButtonByLastNumbers(const QString &lastNumbers);
|
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
|
// Returns all cards visible on the home screen as MaterialInfo stubs
|
||||||
// (lastNumbers, description, amount filled; id=-1, cardNumber empty)
|
// (lastNumbers, description, amount filled; id=-1, cardNumber empty)
|
||||||
QList<MaterialInfo> parseCardsOnHomeScreen();
|
QList<MaterialInfo> parseCardsOnHomeScreen();
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user