// ozon/get_last_transactions.js // // JS-порт Ozon::GetLastTransactionsScript. Логика 1:1 с C++ (см. // android/ozon/GetLastTransactionsScript.cpp). Самый длинный из Ozon-скриптов: // навигация до "Операции" → либо поиск конкретной транзакции по сумме+времени, // либо сканирование последних N транзакций. // // Контракт: // params.deviceId — серийный или android_id // params.appCode — "ozon" // params.maxCount — 0 = фильтр по 24ч, >0 = N последних // params.searchAmount — сумма в рублях для поиска (0 = scan-mode) // params.searchTime — ISO-строка времени (UTC, "" если scan-mode) // params.materialId — UUID задачи (для дампа) // // Особенности порта: // - таймзону устройства игнорируем, используем MSK (+3ч) — Ozon только в РФ. // В C++ берётся getDeviceTimezone(), но в 99% случаев это всё равно МСК. // - host.script.emitTransactionParsed(jsonStr) шлёт результат в EventHandler. // - OCR для bank_transaction_id (когда XML не отдал) — через host.ocr. var MSK_OFFSET_MIN = 180; // +03:00 var MONTH_MAP = { "января": 1, "февраля": 2, "марта": 3, "апреля": 4, "мая": 5, "июня": 6, "июля": 7, "августа": 8, "сентября": 9, "октября": 10, "ноября": 11, "декабря": 12 }; function run(host) { var deviceId = host.params.deviceId; var appCode = host.params.appCode; var maxCount = host.params.maxCount | 0; var searchAmount = +host.params.searchAmount || 0; var searchTimeIso = host.params.searchTime || ""; var materialId = host.params.materialId || ""; var device = host.dao.deviceGetById(deviceId); if (!device || !device.id) device = host.dao.deviceFindByAdbSerial(deviceId); if (device && device.adbSerial) deviceId = device.adbSerial; var app = host.dao.bankProfileGet(device.id, appCode); if (!device || !device.id || !app || app.id < 0) { var msg = "Device or app not found: " + deviceId + " " + appCode; host.log.error(msg); return msg; } host.dump.beginScope("ozon", "FETCH_TRANSACTIONS", deviceId, { materialId: materialId, amount: searchAmount, }); var counter = {n: 0}; try { if (!navigateToTransactionList(host, device, app, deviceId, counter)) { var navErr = "Transaction list screen not loaded: " + deviceId; host.dump.endScope(navErr); return navErr; } var accountId = -1; var accounts = host.dao.materialGetAccounts(device.id, appCode); if (accounts.length > 0) accountId = accounts[0].id; else host.log.warn("No account found for " + device.id + " " + appCode); var result; if (Math.abs(searchAmount) > 0.01) { result = searchTransaction(host, device, deviceId, appCode, accountId, searchAmount, searchTimeIso, counter); } else { result = scanRecentTransactions(host, device, deviceId, appCode, accountId, maxCount, counter); } host.dump.endScope(result || ""); return result; } catch (e) { host.dump.endScope(String(e)); throw e; } } // ─── navigateToTransactionList ───────────────────────────────────────────── function navigateToTransactionList(host, device, app, deviceId, counter) { function tryRefreshIfError() { var refreshBtn = host.parser.findNodeByContentDesc ? host.parser.findNodeByContentDesc("Обновить") : findNodeByDescFallback(host, "Обновить"); if (refreshBtn && refreshBtn.x > 0 && refreshBtn.y > 0) { host.log.debug("Page load error, tapping 'Обновить'"); host.adb.makeTap(deviceId, refreshBtn.x, refreshBtn.y); host.timer.sleep(5000); host.parser.parseAndSaveXml(host.adb.getScreenDumpAsXml(deviceId, ++counter.n)); } } host.parser.parseAndSaveXml(host.adb.getScreenDumpAsXml(deviceId, ++counter.n)); tryRefreshIfError(); var adNode = host.parser.tryToFindAdBanner(); if (!adNode.isEmpty) { host.adb.makeTap(deviceId, adNode.x, adNode.y); host.timer.sleep(1000); host.parser.parseAndSaveXml(host.adb.getScreenDumpAsXml(deviceId, ++counter.n)); var stillAd = host.parser.tryToFindAdBanner(); if (!stillAd.isEmpty) { host.adb.goBack(deviceId); host.timer.sleep(1000); } } host.parser.parseAndSaveXml(host.adb.getScreenDumpAsXml(deviceId, ++counter.n)); // Уже на экране операций — скроллим к началу if (host.parser.isTransactionListScreen()) { host.log.debug("Already on tx list, scrolling to top"); for (var i = 0; i < 5; ++i) { host.adb.makeSwipeWithDuration(deviceId, Math.floor(device.screenWidth / 2), Math.floor(device.screenHeight / 3), Math.floor(device.screenWidth / 2), Math.floor(device.screenHeight * 2 / 3), 200); host.timer.sleep(500); } host.timer.sleep(1000); return true; } // Не на домашнем → перезапуск if (!host.parser.isHomeScreen()) { if (!host.helpers.runAppAndGoToHomeScreen(deviceId, app.package, app.pinCode, device.screenWidth, device.screenHeight)) { host.log.warn("Cannot reach home screen"); host.adb.tryToKillApplication(deviceId, app.package); return false; } host.parser.parseAndSaveXml(host.adb.getScreenDumpAsXml(deviceId, ++counter.n)); tryRefreshIfError(); } host.helpers.tryToCloseAllBannersOnHomePage(deviceId, device.screenWidth, device.screenHeight); host.timer.sleep(500); host.parser.parseAndSaveXml(host.adb.getScreenDumpAsXml(deviceId, ++counter.n)); // Скроллим до "Все" и тапаем var navBarTop = device.screenHeight - Math.floor(device.screenHeight / 10); var allBtn = {isEmpty: true}; for (var j = 0; j < 10; ++j) { if (host.timer.isInterrupted()) return false; var x = Math.floor(device.screenWidth / 2); host.adb.makeSwipeWithDuration(deviceId, x, device.screenHeight - 200, x, 200, 100); host.timer.sleep(1500); host.parser.parseAndSaveXml(host.adb.getScreenDumpAsXml(deviceId, ++counter.n)); host.helpers.closeGooglePlayBannerIfVisible(deviceId, device.screenWidth, device.screenHeight); allBtn = host.parser.findButtonNode("Все", false); if (!allBtn.isEmpty && allBtn.y > 0 && allBtn.y < navBarTop) break; allBtn = {isEmpty: true}; } if (allBtn.isEmpty) return false; host.adb.makeTap(deviceId, allBtn.x, allBtn.y); host.timer.sleep(2000); // Ждём экран "Операции" for (var a = 0; a < 10; ++a) { if (host.timer.isInterrupted()) return false; host.parser.parseAndSaveXml(host.adb.getScreenDumpAsXml(deviceId, ++counter.n)); host.helpers.closeGooglePlayBannerIfVisible(deviceId, device.screenWidth, device.screenHeight); if (host.parser.isTransactionListScreen()) return true; host.timer.sleep(1500); } return false; } // Fallback: ищем по content-desc через findAllNodes если bridge не имеет findNodeByContentDesc. function findNodeByDescFallback(host, desc) { var all = host.parser.findAllNodes(); for (var i = 0; i < all.length; ++i) { if (all[i].contentDesc === desc) return all[i]; } return {isEmpty: true, x: -1, y: -1}; } // ─── Дата-хелперы ────────────────────────────────────────────────────────── // Парсит заголовок "Сегодня"/"Вчера"/"DD месяца, день_недели" в Date (00:00 MSK). function parseDateHeader(text, searchYear) { var t = (text || "").trim(); var nowMsk = nowMskDate(); if (t.toLowerCase() === "сегодня") return atMidnightMsk(nowMsk); if (t.toLowerCase() === "вчера") return atMidnightMsk(addDays(nowMsk, -1)); var m = t.match(/(\d{1,2})\s+(\S+),\s+\S+/); if (!m) return null; var day = parseInt(m[1], 10); var monName = m[2].toLowerCase(); var mon = MONTH_MAP[monName] || 0; if (mon === 0) return null; return new Date(Date.UTC(searchYear, mon - 1, day) - MSK_OFFSET_MIN * 60 * 1000); } function nowMskDate() { return new Date(Date.now() + MSK_OFFSET_MIN * 60 * 1000); // shift to MSK } function atMidnightMsk(d) { // d уже в MSK-shifted виде return new Date(Date.UTC(d.getUTCFullYear(), d.getUTCMonth(), d.getUTCDate()) - MSK_OFFSET_MIN * 60 * 1000); } function addDays(d, n) { return new Date(d.getTime() + n * 86400000); } function sameDate(a, b) { if (!a || !b) return false; return a.getTime() === b.getTime(); } function isBefore(a, b) { return a && b && a.getTime() < b.getTime(); } function formatDateMsk(d) { var msk = new Date(d.getTime() + MSK_OFFSET_MIN * 60 * 1000); function pad(n) { return n < 10 ? '0' + n : '' + n; } return pad(msk.getUTCDate()) + "." + pad(msk.getUTCMonth() + 1) + "." + msk.getUTCFullYear(); } // ─── searchTransaction ───────────────────────────────────────────────────── function searchTransaction(host, device, deviceId, appCode, accountId, searchAmount, searchTimeIso, counter) { var maxScrolls = 30; var searchTime = searchTimeIso ? new Date(searchTimeIso) : null; if (!searchTime || isNaN(searchTime.getTime())) return "Invalid searchTime"; var searchDate = atMidnightMsk(new Date(searchTime.getTime() + MSK_OFFSET_MIN * 60 * 1000)); var searchYear = new Date(searchTime.getTime() + MSK_OFFSET_MIN * 60 * 1000).getUTCFullYear(); function detectNavBarTop() { var nodes = host.parser.findAllNodes(); for (var i = 0; i < nodes.length; ++i) { var n = nodes[i]; if (n.resourceId === "ru.ozon.fintech.finance:id/main_activity_bottom_navigation_holder" && n.height > 0) return n.y1; } return device.screenHeight - Math.floor(device.screenHeight / 10); } var navBarTop = detectNavBarTop(); host.log.debug("Searching: amount=" + searchAmount + " date=" + formatDateMsk(searchDate)); // Считаем сколько дат на каждой y-координате (для детекции схлопнутых) function computeDateYCount() { var cnt = {}; var nodes = host.parser.findAllNodes(); for (var i = 0; i < nodes.length; ++i) { var n = nodes[i]; if (n.className !== "android.widget.TextView") continue; if (n.y <= 0) continue; if (parseDateHeader(n.text, searchYear)) { cnt[n.y] = (cnt[n.y] || 0) + 1; } } return cnt; } function hasDateOnScreen() { var yc = computeDateYCount(); var nodes = host.parser.findAllNodes(); for (var i = 0; i < nodes.length; ++i) { var n = nodes[i]; if (n.className !== "android.widget.TextView") continue; if (n.y <= 0) continue; if ((yc[n.y] || 0) !== 1) continue; var d = parseDateHeader(n.text, searchYear); if (d && sameDate(d, searchDate)) return true; } return false; } function hasOlderDateOnScreen() { var yc = computeDateYCount(); var nodes = host.parser.findAllNodes(); for (var i = 0; i < nodes.length; ++i) { var n = nodes[i]; if (n.className !== "android.widget.TextView") continue; if (n.y <= 0) continue; if ((yc[n.y] || 0) !== 1) continue; var d = parseDateHeader(n.text, searchYear); if (d && isBefore(d, searchDate)) return true; } return false; } // Ждём WebView accessibility tree for (var w = 0; w < 5; ++w) { host.parser.parseAndSaveXml(host.adb.getScreenDumpAsXml(deviceId, ++counter.n)); host.helpers.closeGooglePlayBannerIfVisible(deviceId, device.screenWidth, device.screenHeight); if (host.parser.findTransactionButtons(1).length > 0) break; if (hasDateOnScreen()) break; host.timer.sleep(2000); } // Скроллим до нужной даты var dateFound = false; for (var s = 0; s < maxScrolls; ++s) { if (host.timer.isInterrupted()) return "Interrupted"; host.parser.parseAndSaveXml(host.adb.getScreenDumpAsXml(deviceId, ++counter.n)); host.helpers.closeGooglePlayBannerIfVisible(deviceId, device.screenWidth, device.screenHeight); if (hasDateOnScreen()) { dateFound = true; break; } if (hasOlderDateOnScreen()) { host.log.debug("Passed target date"); break; } host.adb.makeSwipeWithDuration(deviceId, Math.floor(device.screenWidth / 2), Math.floor(device.screenHeight * 2 / 3), Math.floor(device.screenWidth / 2), Math.floor(device.screenHeight / 3), 600); host.timer.sleep(1500); } if (!dateFound) { return "Date not found in transaction list: " + formatDateMsk(searchDate); } function findDateYRange() { var yc = computeDateYCount(); function isVisible(y) { return y > 0 && (yc[y] || 0) === 1; } var dateY = -1; var nextDateY = device.screenHeight; var foundOlder = false; var nodes = host.parser.findAllNodes(); for (var i = 0; i < nodes.length; ++i) { var n = nodes[i]; if (n.className !== "android.widget.TextView") continue; var d = parseDateHeader(n.text, searchYear); if (!d) continue; if (sameDate(d, searchDate)) { if (isVisible(n.y)) dateY = n.y; } else if (isBefore(d, searchDate)) { foundOlder = true; if (isVisible(n.y) && (dateY < 0 || n.y > dateY) && n.y < nextDateY) { nextDateY = n.y; } } } if (dateY < 0 && nextDateY < device.screenHeight) dateY = 0; return [dateY, nextDateY]; } var checked = {}; var noProgress = 0; for (var attempt = 0; attempt < 15; ++attempt) { if (host.timer.isInterrupted()) return "Interrupted"; host.parser.parseAndSaveXml(host.adb.getScreenDumpAsXml(deviceId, ++counter.n)); host.helpers.closeGooglePlayBannerIfVisible(deviceId, device.screenWidth, device.screenHeight); var range = findDateYRange(); var dateHeaderY = range[0], nextDateHeaderY = range[1]; if (dateHeaderY < 0) { host.adb.makeSwipeWithDuration(deviceId, Math.floor(device.screenWidth / 2), Math.floor(device.screenHeight * 2 / 3), Math.floor(device.screenWidth / 2), Math.floor(device.screenHeight / 3), 500); host.timer.sleep(1500); continue; } if (dateHeaderY === 0 && nextDateHeaderY < device.screenHeight) { host.adb.makeSwipeWithDuration(deviceId, Math.floor(device.screenWidth / 2), Math.floor(device.screenHeight / 3), Math.floor(device.screenWidth / 2), Math.floor(device.screenHeight * 2 / 3), 600); host.timer.sleep(1500); continue; } var txButtons = host.parser.findTransactionButtons(20); var tapped = false, hasNewButton = false, buttonsInRange = 0; for (var bi = 0; bi < txButtons.length; ++bi) { if (host.timer.isInterrupted()) return "Interrupted"; var btn = txButtons[bi]; if (btn.y1 <= dateHeaderY || btn.y1 >= nextDateHeaderY) continue; if (btn.y1 <= 0 || btn.y1 >= navBarTop) continue; ++buttonsInRange; var parsed = host.parser.parseTransactionButtonText(btn.text); if (Math.abs(parsed.amount - searchAmount) > 0.01) continue; var safeMaxY = navBarTop - 20; var tapY = btn.y; if (tapY > safeMaxY) tapY = Math.max(btn.y1 + 30, safeMaxY); host.adb.makeTap(deviceId, btn.x, tapY); host.timer.sleep(2000); var detail = {bankTime: ""}; for (var da = 0; da < 10; ++da) { if (host.timer.isInterrupted()) return "Interrupted"; host.parser.parseAndSaveXml(host.adb.getScreenDumpAsXml(deviceId, ++counter.n)); host.helpers.closeGooglePlayBannerIfVisible(deviceId, device.screenWidth, device.screenHeight); detail = host.parser.parseTransactionDetail(); if (detail.bankTime) break; if (host.parser.isTransactionListScreen()) { host.adb.makeTap(deviceId, btn.x, btn.y); } host.timer.sleep(1500); } if (!detail.bankTime) { host.adb.goBack(deviceId); host.timer.sleep(1000); continue; } var dtStr = detail.bankTime.replace(/[     ]/g, ' '); var txTime = parseBankTime(dtStr); if (checked[dtStr]) { host.adb.goBack(deviceId); host.timer.sleep(500); continue; } checked[dtStr] = true; tapped = true; hasNewButton = true; // Дата принадлежит нужной? if (txTime) { var txMsk = new Date(txTime.getTime() + MSK_OFFSET_MIN * 60 * 1000); var txDate = new Date(Date.UTC(txMsk.getUTCFullYear(), txMsk.getUTCMonth(), txMsk.getUTCDate()) - MSK_OFFSET_MIN * 60 * 1000); if (!sameDate(txDate, searchDate)) { host.adb.goBack(deviceId); host.timer.sleep(1000); continue; } // ±10 минут if (searchTime) { var diffSec = Math.abs((txTime.getTime() - searchTime.getTime()) / 1000); if (diffSec > 600) { host.adb.goBack(deviceId); host.timer.sleep(1000); continue; } } } host.log.debug("FOUND transaction: amount=" + parsed.amount + " time=" + dtStr); // Скриншот деталей для receipt host.adb.captureScreenshotBase64(deviceId); var externalId = detail.bankTrExternalId || ""; if (accountId !== -1) { var fallbackId = externalId || ("ozon_" + dtStr.replace(/,/g, '').trim().replace(/ /g, '_')); var tx = { accountId: accountId, amount: parsed.amount, bankName: appCode, bankTime: dtStr, bankTrExternalId: fallbackId, status: "complete", timestamp: new Date().toISOString(), }; if (txTime) tx.completeTime = txTime.toISOString(); if (detail.name) tx.name = detail.name; if (detail.description) tx.description = detail.description; if (detail.phone) { tx.phone = (detail.phone || "").replace(/[^0-9]/g, ''); tx.type = "phone"; } host.dao.transactionInsert(tx); } // Шлём в EventHandler через signalSink var finalId = externalId || ("ozon_" + dtStr.replace(/,/g, '').trim().replace(/ /g, '_')); var emitObj = { bank_transaction_id: finalId, amount: Math.round(parsed.amount * 100), currency: 643, transaction_type: "bank", bank_participator_id: detail.phone || detail.name || "", status: "completed", extra: {fee: 0}, created_at: txTime ? txTime.toISOString() : null, }; host.script.emitTransactionParsed(JSON.stringify(emitObj)); host.adb.goBack(deviceId); host.timer.sleep(1000); host.adb.goBack(deviceId); host.timer.sleep(1000); return ""; // success } if (!hasNewButton && tapped) { if (++noProgress >= 5) break; } else if (hasNewButton) noProgress = 0; if (!tapped || !hasNewButton) { var cx = Math.floor(device.screenWidth / 2); if (dateHeaderY === 0 && nextDateHeaderY < device.screenHeight) { host.adb.makeSwipeWithDuration(deviceId, cx, Math.floor(device.screenHeight * 2 / 5), cx, Math.floor(device.screenHeight * 3 / 4), 600); host.timer.sleep(1500); } else { var safeBottom = navBarTop - 30; var amplitude; if (dateHeaderY > 0 && dateHeaderY < safeBottom - 100) { var safeTop = Math.max(dateHeaderY + 80, Math.floor(device.screenHeight / 4)); amplitude = Math.min(safeBottom - safeTop, 700); } else { amplitude = Math.min(safeBottom - Math.floor(device.screenHeight / 4), 700); } amplitude = Math.max(amplitude, 300); for (var sw = 0; sw < 3; ++sw) { host.adb.makeSwipeWithDuration(deviceId, cx, safeBottom, cx, safeBottom - amplitude, 250); host.timer.sleep(350); } host.timer.sleep(800); } } } return "Transaction not found: amount=" + searchAmount.toFixed(2) + " date=" + formatDateMsk(searchDate); } // Парсит "dd.MM.yyyy, HH:mm" или "dd.MM.yyyy,HH:mm" → UTC Date (MSK-localized). function parseBankTime(s) { var m = (s || "").match(/(\d{2})\.(\d{2})\.(\d{4}),?\s*(\d{2}):(\d{2})/); if (!m) return null; var d = parseInt(m[1], 10), mo = parseInt(m[2], 10), y = parseInt(m[3], 10); var hh = parseInt(m[4], 10), mm = parseInt(m[5], 10); return new Date(Date.UTC(y, mo - 1, d, hh, mm) - MSK_OFFSET_MIN * 60 * 1000); } // ─── scanRecentTransactions ──────────────────────────────────────────────── function scanRecentTransactions(host, device, deviceId, appCode, accountId, maxCount, counter) { var navBarTop = device.screenHeight - Math.floor(device.screenHeight / 10); // Ждём появления транзакций var txButtons = []; for (var i = 0; i < 5; ++i) { host.parser.parseAndSaveXml(host.adb.getScreenDumpAsXml(deviceId, ++counter.n)); host.helpers.closeGooglePlayBannerIfVisible(deviceId, device.screenWidth, device.screenHeight); txButtons = host.parser.findTransactionButtons(8); if (txButtons.length > 0) break; host.timer.sleep(1500); } var transactions = []; for (var i2 = 0; i2 < txButtons.length; ++i2) { var btn = txButtons[i2]; if (btn.y <= 0 || btn.y >= navBarTop) { for (var s = 0; s < 5; ++s) { var x = Math.floor(device.screenWidth / 2); var offset = Math.floor(device.screenHeight / 4); host.adb.makeSwipe(deviceId, x, device.screenHeight - offset, x, offset); host.timer.sleep(1000); host.parser.parseAndSaveXml(host.adb.getScreenDumpAsXml(deviceId, ++counter.n)); host.helpers.closeGooglePlayBannerIfVisible(deviceId, device.screenWidth, device.screenHeight); txButtons = host.parser.findTransactionButtons(8); if (i2 < txButtons.length && txButtons[i2].y > 0 && txButtons[i2].y < navBarTop) break; } if (i2 >= txButtons.length) break; } var visibleBtn = txButtons[i2]; var tx = host.parser.parseTransactionButtonText(visibleBtn.text); host.adb.makeTap(deviceId, visibleBtn.x, visibleBtn.y); host.timer.sleep(2000); var detail = {bankTime: ""}; for (var da = 0; da < 10; ++da) { host.parser.parseAndSaveXml(host.adb.getScreenDumpAsXml(deviceId, ++counter.n)); host.helpers.closeGooglePlayBannerIfVisible(deviceId, device.screenWidth, device.screenHeight); detail = host.parser.parseTransactionDetail(); if (detail.bankTime) break; if (host.parser.isTransactionListScreen()) { host.adb.makeTap(deviceId, visibleBtn.x, visibleBtn.y); } host.timer.sleep(1500); } tx.bankName = appCode; tx.bankTime = detail.bankTime; tx.bankTrExternalId = detail.bankTrExternalId || ""; // Если ID не найден — пробуем через "Документы" + OCR if (!tx.bankTrExternalId) { var docBtn = host.parser.findButtonNode("Документы", false); if (!docBtn.isEmpty) { host.adb.makeTap(deviceId, docBtn.x, docBtn.y); host.timer.sleep(2000); var docLoaded = false; for (var di = 0; di < 15; ++di) { host.parser.parseAndSaveXml(host.adb.getScreenDumpAsXml(deviceId, ++counter.n)); host.helpers.closeGooglePlayBannerIfVisible(deviceId, device.screenWidth, device.screenHeight); if (!findNodeByDescFallback(host, "Поделиться").isEmpty) { docLoaded = true; break; } host.timer.sleep(1500); } if (docLoaded) { host.timer.sleep(500); var ocrText = host.ocr.recognizeFromScreen(deviceId); if (ocrText) { tx.bankTrExternalId = host.ocr.extractTransactionId(ocrText) || ""; } } host.adb.goBack(deviceId); host.timer.sleep(1000); } } tx.status = "complete"; tx.timestamp = new Date().toISOString(); if (detail.name) tx.name = detail.name; if (detail.phone) { tx.phone = (detail.phone || "").replace(/[^0-9]/g, ''); tx.type = "phone"; } if (tx.bankTime) { var dtStr = tx.bankTime.replace(/[     ]/g, ' '); tx.bankTime = dtStr; var parsed = parseBankTime(dtStr); if (parsed) tx.completeTime = parsed.toISOString(); } // Фильтр по времени (24ч) if (maxCount <= 0 && tx.completeTime) { var ageSec = (Date.now() - new Date(tx.completeTime).getTime()) / 1000; if (ageSec > 86400) { host.adb.goBack(deviceId); host.timer.sleep(1000); break; } } if (accountId !== -1) { tx.accountId = accountId; host.dao.transactionInsert(tx); // Receipt + OCR-id через "Документы" — упрощённо, без двойного перехода. // Если уже извлекли ID выше — не повторяем. } transactions.push(tx); if (maxCount > 0 && transactions.length >= maxCount) { host.adb.goBack(deviceId); host.timer.sleep(1000); break; } host.adb.goBack(deviceId); host.timer.sleep(1500); for (var wa = 0; wa < 5; ++wa) { host.parser.parseAndSaveXml(host.adb.getScreenDumpAsXml(deviceId, ++counter.n)); host.helpers.closeGooglePlayBannerIfVisible(deviceId, device.screenWidth, device.screenHeight); if (host.parser.isTransactionListScreen()) break; host.timer.sleep(1000); } host.parser.parseAndSaveXml(host.adb.getScreenDumpAsXml(deviceId, ++counter.n)); txButtons = host.parser.findTransactionButtons(8); } return ""; // success }