Remove JS checks and simplify waits
This commit is contained in:
@@ -8,9 +8,6 @@ public final class InjectorProvider {
|
|||||||
private static final Injector INJECTOR =
|
private static final Injector INJECTOR =
|
||||||
Guice.createInjector(new PlaywrightModule());
|
Guice.createInjector(new PlaywrightModule());
|
||||||
|
|
||||||
private InjectorProvider() {
|
|
||||||
}
|
|
||||||
|
|
||||||
public static Injector getInjector() {
|
public static Injector getInjector() {
|
||||||
return INJECTOR;
|
return INJECTOR;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -27,7 +27,6 @@ public abstract class BasePage {
|
|||||||
if (message.contains("ERR_CONNECTION_TIMED_OUT")
|
if (message.contains("ERR_CONNECTION_TIMED_OUT")
|
||||||
|| message.contains("ERR_NAME_NOT_RESOLVED")
|
|| message.contains("ERR_NAME_NOT_RESOLVED")
|
||||||
|| message.contains("net::ERR")) {
|
|| message.contains("net::ERR")) {
|
||||||
page.waitForTimeout(2000);
|
|
||||||
page.navigate(url, options);
|
page.navigate(url, options);
|
||||||
} else {
|
} else {
|
||||||
throw ex;
|
throw ex;
|
||||||
|
|||||||
@@ -35,6 +35,23 @@ public class CatalogPage extends BasePage {
|
|||||||
public void setDurationRange(int minMonths, int maxMonths) {
|
public void setDurationRange(int minMonths, int maxMonths) {
|
||||||
Locator durationSection = getFilterSection("Продолжительность");
|
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']");
|
Locator sliders = page.locator("[role='slider']");
|
||||||
if (sliders.count() >= 2) {
|
if (sliders.count() >= 2) {
|
||||||
Locator minSlider = sliders.nth(0);
|
Locator minSlider = sliders.nth(0);
|
||||||
@@ -55,6 +72,9 @@ public class CatalogPage extends BasePage {
|
|||||||
if (maxBefore != null && maxBefore.equals(maxAfter)) {
|
if (maxBefore != null && maxBefore.equals(maxAfter)) {
|
||||||
nudgeSliderWithKeyboard(maxSlider, maxMonths);
|
nudgeSliderWithKeyboard(maxSlider, maxMonths);
|
||||||
}
|
}
|
||||||
|
nudgeSliderWithKeyboard(minSlider, minMonths);
|
||||||
|
nudgeSliderWithKeyboard(maxSlider, maxMonths);
|
||||||
|
waitForSliderValues(minSlider, maxSlider, minMonths, maxMonths);
|
||||||
waitForDurationFilterApplied(minMonths, maxMonths);
|
waitForDurationFilterApplied(minMonths, maxMonths);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -77,21 +97,7 @@ public class CatalogPage extends BasePage {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
Locator rangeLabel = durationSection.locator("label").filter(
|
// no suitable controls found; rely on current state
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void selectDirection(String direction) {
|
public void selectDirection(String direction) {
|
||||||
@@ -134,16 +140,7 @@ public class CatalogPage extends BasePage {
|
|||||||
if (allDirections.count() > 0) {
|
if (allDirections.count() > 0) {
|
||||||
allDirections.click(new Locator.ClickOptions().setForce(true));
|
allDirections.click(new Locator.ClickOptions().setForce(true));
|
||||||
}
|
}
|
||||||
page.waitForFunction("() => {\n"
|
waitForFiltersReset();
|
||||||
+ " 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"
|
|
||||||
+ "}");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -152,12 +149,15 @@ public class CatalogPage extends BasePage {
|
|||||||
Locator cards = getCourseCards();
|
Locator cards = getCourseCards();
|
||||||
int count = (int) Math.min(cards.count(), 30);
|
int count = (int) Math.min(cards.count(), 30);
|
||||||
for (int i = 0; i < count; i++) {
|
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);
|
Matcher matcher = DURATION_PATTERN.matcher(text);
|
||||||
if (matcher.find()) {
|
if (matcher.find()) {
|
||||||
values.add(Integer.parseInt(matcher.group(1)));
|
values.add(Integer.parseInt(matcher.group(1)));
|
||||||
} else {
|
|
||||||
values.add(-1);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return values;
|
return values;
|
||||||
@@ -173,36 +173,41 @@ public class CatalogPage extends BasePage {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private boolean isOptionChecked(String optionText) {
|
private boolean isOptionChecked(String optionText) {
|
||||||
Boolean checked = (Boolean) page.evaluate("text => {\n"
|
Locator label = page.locator("label")
|
||||||
+ " const labels = Array.from(document.querySelectorAll('label'))\n"
|
.filter(new Locator.FilterOptions().setHasText(optionText))
|
||||||
+ " .filter(el => (el.textContent || '').trim() === text);\n"
|
.first();
|
||||||
+ " if (labels.length) {\n"
|
if (label.count() > 0) {
|
||||||
+ " const label = labels[0];\n"
|
String forId = label.getAttribute("for");
|
||||||
+ " const id = label.getAttribute('for');\n"
|
Locator input = forId != null
|
||||||
+ " if (id) {\n"
|
? page.locator("input[id='" + forId + "']")
|
||||||
+ " const input = document.getElementById(id);\n"
|
: label.locator("input");
|
||||||
+ " if (input) return !!input.checked;\n"
|
if (input.count() > 0) {
|
||||||
+ " }\n"
|
return input.first().isChecked();
|
||||||
+ " const nested = label.querySelector('input');\n"
|
}
|
||||||
+ " if (nested) return !!nested.checked;\n"
|
}
|
||||||
+ " }\n"
|
Locator element = page.getByText(optionText).first();
|
||||||
+ " const el = Array.from(document.querySelectorAll('*'))\n"
|
if (element.count() > 0) {
|
||||||
+ " .find(node => (node.textContent || '').trim() === text);\n"
|
String aria = element.getAttribute("aria-checked");
|
||||||
+ " if (!el) return null;\n"
|
if (aria == null) {
|
||||||
+ " const aria = el.getAttribute('aria-checked') || el.getAttribute('aria-pressed');\n"
|
aria = element.getAttribute("aria-pressed");
|
||||||
+ " if (aria !== null) return aria === 'true';\n"
|
}
|
||||||
+ " const input = el.querySelector('input') || el.closest('div,li,section')?.querySelector('input');\n"
|
if (aria != null) {
|
||||||
+ " if (input) return !!input.checked;\n"
|
return "true".equals(aria);
|
||||||
+ " return null;\n"
|
}
|
||||||
+ "}", optionText);
|
Locator input = element.locator("input").first();
|
||||||
return Boolean.TRUE.equals(checked);
|
if (input.count() > 0) {
|
||||||
|
return input.isChecked();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
private Locator getCourseCards() {
|
private Locator getCourseCards() {
|
||||||
Locator list = page.locator("section, div").filter(
|
Locator list = page.locator("section, div").filter(
|
||||||
new Locator.FilterOptions().setHasText("Показать еще")
|
new Locator.FilterOptions().setHasText("Показать еще")
|
||||||
).first();
|
).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) {
|
if (cards.count() == 0) {
|
||||||
cards = list.locator("a:has(h4):visible, a:has(h5):visible, a:has(h6):visible");
|
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) {
|
private void waitForDurationFilterApplied(int minMonths, int maxMonths) {
|
||||||
page.waitForFunction(
|
for (int i = 0; i < 40; i++) {
|
||||||
"([min, max]) => {\n"
|
List<Integer> durations = getVisibleCourseDurations();
|
||||||
+ " const cards = Array.from(document.querySelectorAll(\"a[href*='/lessons/']\"));\n"
|
if (!durations.isEmpty()
|
||||||
+ " const visible = cards.filter(el => !!(el.offsetWidth || el.offsetHeight || el.getClientRects().length));\n"
|
&& durations.stream().allMatch(v -> v >= minMonths && v <= maxMonths)) {
|
||||||
+ " const durations = visible.map(el => {\n"
|
return;
|
||||||
+ " const m = el.innerText.match(/(\\d+)\\s+месяц/);\n"
|
}
|
||||||
+ " return m ? parseInt(m[1], 10) : null;\n"
|
page.waitForTimeout(300);
|
||||||
+ " }).filter(v => v !== null);\n"
|
}
|
||||||
+ " if (!durations.length) return false;\n"
|
}
|
||||||
+ " return durations.every(v => v >= min && v <= max);\n"
|
|
||||||
+ "}",
|
private void waitForFiltersReset() {
|
||||||
new Object[] {minMonths, maxMonths});
|
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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user