diff --git a/src/test/java/ru/kovbasa/config/InjectorProvider.java b/src/test/java/ru/kovbasa/config/InjectorProvider.java index 4115d4e..2ecfc78 100644 --- a/src/test/java/ru/kovbasa/config/InjectorProvider.java +++ b/src/test/java/ru/kovbasa/config/InjectorProvider.java @@ -8,9 +8,6 @@ public final class InjectorProvider { private static final Injector INJECTOR = Guice.createInjector(new PlaywrightModule()); - private InjectorProvider() { - } - public static Injector getInjector() { return INJECTOR; } diff --git a/src/test/java/ru/kovbasa/pages/BasePage.java b/src/test/java/ru/kovbasa/pages/BasePage.java index 3f016cf..ab37d5c 100644 --- a/src/test/java/ru/kovbasa/pages/BasePage.java +++ b/src/test/java/ru/kovbasa/pages/BasePage.java @@ -27,7 +27,6 @@ public abstract class BasePage { if (message.contains("ERR_CONNECTION_TIMED_OUT") || message.contains("ERR_NAME_NOT_RESOLVED") || message.contains("net::ERR")) { - page.waitForTimeout(2000); page.navigate(url, options); } else { throw ex; diff --git a/src/test/java/ru/kovbasa/pages/CatalogPage.java b/src/test/java/ru/kovbasa/pages/CatalogPage.java index 27b70fb..a717e72 100644 --- a/src/test/java/ru/kovbasa/pages/CatalogPage.java +++ b/src/test/java/ru/kovbasa/pages/CatalogPage.java @@ -35,6 +35,23 @@ public class CatalogPage extends BasePage { public void setDurationRange(int minMonths, int maxMonths) { Locator durationSection = getFilterSection("Продолжительность"); + Locator rangeLabel = durationSection.locator("label").filter( + new Locator.FilterOptions().setHasText(String.valueOf(minMonths)) + ).filter(new Locator.FilterOptions().setHasText(String.valueOf(maxMonths))).first(); + if (rangeLabel.count() > 0) { + rangeLabel.click(); + waitForDurationFilterApplied(minMonths, maxMonths); + return; + } + + Locator rangeText = durationSection.locator( + "text=/\\b" + minMonths + "\\b.*\\b" + maxMonths + "\\b/").first(); + if (rangeText.count() > 0) { + rangeText.click(); + waitForDurationFilterApplied(minMonths, maxMonths); + return; + } + Locator sliders = page.locator("[role='slider']"); if (sliders.count() >= 2) { Locator minSlider = sliders.nth(0); @@ -55,6 +72,9 @@ public class CatalogPage extends BasePage { if (maxBefore != null && maxBefore.equals(maxAfter)) { nudgeSliderWithKeyboard(maxSlider, maxMonths); } + nudgeSliderWithKeyboard(minSlider, minMonths); + nudgeSliderWithKeyboard(maxSlider, maxMonths); + waitForSliderValues(minSlider, maxSlider, minMonths, maxMonths); waitForDurationFilterApplied(minMonths, maxMonths); return; } @@ -77,21 +97,7 @@ public class CatalogPage extends BasePage { return; } - Locator rangeLabel = durationSection.locator("label").filter( - new Locator.FilterOptions().setHasText(String.valueOf(minMonths)) - ).filter(new Locator.FilterOptions().setHasText(String.valueOf(maxMonths))).first(); - if (rangeLabel.count() > 0) { - rangeLabel.click(); - return; - } - - Locator rangeText = durationSection.locator( - "text=/\\b" + minMonths + "\\b.*\\b" + maxMonths + "\\b/").first(); - if (rangeText.count() > 0) { - rangeText.click(); - waitForDurationFilterApplied(minMonths, maxMonths); - return; - } + // no suitable controls found; rely on current state } public void selectDirection(String direction) { @@ -134,16 +140,7 @@ public class CatalogPage extends BasePage { if (allDirections.count() > 0) { allDirections.click(new Locator.ClickOptions().setForce(true)); } - page.waitForFunction("() => {\n" - + " const label = Array.from(document.querySelectorAll('label'))\n" - + " .find(el => (el.textContent || '').trim() === 'Все направления');\n" - + " if (!label) return false;\n" - + " const id = label.getAttribute('for');\n" - + " const input = id ? document.getElementById(id) : label.querySelector('input');\n" - + " const defaultSelected = input ? input.checked : true;\n" - + " const duration = document.body.innerText.includes('От 0 до 15 месяцев');\n" - + " return defaultSelected && duration;\n" - + "}"); + waitForFiltersReset(); } @@ -152,12 +149,15 @@ public class CatalogPage extends BasePage { Locator cards = getCourseCards(); int count = (int) Math.min(cards.count(), 30); for (int i = 0; i < count; i++) { - String text = cards.nth(i).innerText(); + String text; + try { + text = cards.nth(i).innerText(new Locator.InnerTextOptions().setTimeout(2000)); + } catch (PlaywrightException ex) { + continue; + } Matcher matcher = DURATION_PATTERN.matcher(text); if (matcher.find()) { values.add(Integer.parseInt(matcher.group(1))); - } else { - values.add(-1); } } return values; @@ -173,36 +173,41 @@ public class CatalogPage extends BasePage { } private boolean isOptionChecked(String optionText) { - Boolean checked = (Boolean) page.evaluate("text => {\n" - + " const labels = Array.from(document.querySelectorAll('label'))\n" - + " .filter(el => (el.textContent || '').trim() === text);\n" - + " if (labels.length) {\n" - + " const label = labels[0];\n" - + " const id = label.getAttribute('for');\n" - + " if (id) {\n" - + " const input = document.getElementById(id);\n" - + " if (input) return !!input.checked;\n" - + " }\n" - + " const nested = label.querySelector('input');\n" - + " if (nested) return !!nested.checked;\n" - + " }\n" - + " const el = Array.from(document.querySelectorAll('*'))\n" - + " .find(node => (node.textContent || '').trim() === text);\n" - + " if (!el) return null;\n" - + " const aria = el.getAttribute('aria-checked') || el.getAttribute('aria-pressed');\n" - + " if (aria !== null) return aria === 'true';\n" - + " const input = el.querySelector('input') || el.closest('div,li,section')?.querySelector('input');\n" - + " if (input) return !!input.checked;\n" - + " return null;\n" - + "}", optionText); - return Boolean.TRUE.equals(checked); + Locator label = page.locator("label") + .filter(new Locator.FilterOptions().setHasText(optionText)) + .first(); + if (label.count() > 0) { + String forId = label.getAttribute("for"); + Locator input = forId != null + ? page.locator("input[id='" + forId + "']") + : label.locator("input"); + if (input.count() > 0) { + return input.first().isChecked(); + } + } + Locator element = page.getByText(optionText).first(); + if (element.count() > 0) { + String aria = element.getAttribute("aria-checked"); + if (aria == null) { + aria = element.getAttribute("aria-pressed"); + } + if (aria != null) { + return "true".equals(aria); + } + Locator input = element.locator("input").first(); + if (input.count() > 0) { + return input.isChecked(); + } + } + return false; } private Locator getCourseCards() { Locator list = page.locator("section, div").filter( new Locator.FilterOptions().setHasText("Показать еще") ).first(); - Locator cards = list.locator("a[href*='/lessons/']:visible"); + Locator cards = list.locator("a[href*='/lessons/']:visible") + .filter(new Locator.FilterOptions().setHasNotText("Успеть!")); if (cards.count() == 0) { cards = list.locator("a:has(h4):visible, a:has(h5):visible, a:has(h6):visible"); } @@ -272,17 +277,41 @@ public class CatalogPage extends BasePage { } private void waitForDurationFilterApplied(int minMonths, int maxMonths) { - page.waitForFunction( - "([min, max]) => {\n" - + " const cards = Array.from(document.querySelectorAll(\"a[href*='/lessons/']\"));\n" - + " const visible = cards.filter(el => !!(el.offsetWidth || el.offsetHeight || el.getClientRects().length));\n" - + " const durations = visible.map(el => {\n" - + " const m = el.innerText.match(/(\\d+)\\s+месяц/);\n" - + " return m ? parseInt(m[1], 10) : null;\n" - + " }).filter(v => v !== null);\n" - + " if (!durations.length) return false;\n" - + " return durations.every(v => v >= min && v <= max);\n" - + "}", - new Object[] {minMonths, maxMonths}); + for (int i = 0; i < 40; i++) { + List durations = getVisibleCourseDurations(); + if (!durations.isEmpty() + && durations.stream().allMatch(v -> v >= minMonths && v <= maxMonths)) { + return; + } + page.waitForTimeout(300); + } + } + + private void waitForFiltersReset() { + for (int i = 0; i < 20; i++) { + if (isDefaultDirectionSelected() && getCourseCards().count() > 0) { + return; + } + page.waitForTimeout(300); + } + } + + private void waitForSliderValues(Locator minSlider, Locator maxSlider, int minMonths, int maxMonths) { + for (int i = 0; i < 20; i++) { + String minValue = minSlider.getAttribute("aria-valuenow"); + String maxValue = maxSlider.getAttribute("aria-valuenow"); + if (minValue != null && maxValue != null) { + try { + int min = Integer.parseInt(minValue); + int max = Integer.parseInt(maxValue); + if (min == minMonths && max == maxMonths) { + return; + } + } catch (NumberFormatException ignored) { + // ignore and retry + } + } + page.waitForTimeout(200); + } } }