All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.xlrit.gears.runner.runnertarget.SelenideRunnerTarget Maven / Gradle / Ivy

There is a newer version: 1.17.1
Show newest version
package com.xlrit.gears.runner.runnertarget;

import com.codeborne.selenide.Configuration;
import com.codeborne.selenide.SelenideElement;
import com.codeborne.selenide.WebDriverRunner;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ArrayNode;
import com.fasterxml.jackson.databind.node.JsonNodeType;
import com.xlrit.gears.runner.run.Config;
import com.xlrit.gears.runner.graphql.GraphQLClient;
import com.xlrit.gears.runner.graphql.Operation;
import com.xlrit.gears.runner.utils.GraphQLUtils;
import org.openqa.selenium.TimeoutException;
import org.openqa.selenium.WebElement;
import org.openqa.selenium.support.ui.ExpectedConditions;
import org.openqa.selenium.support.ui.WebDriverWait;

import java.time.Duration;
import java.util.Objects;
import java.util.Optional;
import java.util.logging.Logger;

import static com.codeborne.selenide.Condition.*;
import static com.codeborne.selenide.Selectors.*;
import static com.codeborne.selenide.Selenide.*;
import static com.xlrit.gears.runner.utils.SelenideUtils.*;

public class SelenideRunnerTarget implements RunnerTarget {
	private final Logger logger = Logger.getLogger(SelenideRunnerTarget.class.getName());
	private final GraphQLClient client;
	private final int waitMillis;

	private boolean loggedIn = false;
	private String currentUser = "";

	public SelenideRunnerTarget(Config config, boolean constructorLogin) {
		this.client = new GraphQLClient(config.endpoint, new ObjectMapper());
		this.waitMillis = config.waitMillis;

		// setup Selenide configuration
		Configuration.timeout = config.timeoutMillis;
		Configuration.baseUrl = "http://localhost:8080";
		Configuration.reportsFolder = "target/reports";
		Configuration.headless = config.headless;

		// TODO: is there a way to get authorization token when logging in through web interface?
		if (constructorLogin) graphqlClientLogin();
	}

	private void graphqlClientLogin() {
		JsonNode login = client.invoke(Operation.login("demo", "demo"));
		client.setToken(login.get("login").asText());
		loggedIn = true;
	}

	@Override
	public void loadData(String srcDir, String pattern) {
		JsonNode jsonResponse = client.invoke(Operation.loadData(srcDir, pattern));
		logger.info(String.format("Load data: %s", jsonResponse));
		if (!loggedIn) graphqlClientLogin();
	}

	@Override
	public void dataAssertion(String key, String grql) {}

	@Override
	public void processCompleted(String processKey) {}

	@Override
	public void setDateTime(String value) {}

	@Override
	public void scenarioStart(String scenario) {}

	@Override
	public void scenarioFinish() {}

	@Override
	public void finish() {}

	@Override
	public void login(String identifier) {
		if (currentUser.equals(identifier)) return;
		if (!currentUser.equals("")) logout();
		logger.info(String.format("Logging in as %s", identifier));
		open("/#/login");
		$(byName("username")).setValue(identifier);
		$(byName("password")).setValue(identifier);
		$("#login-submit-button").click();
		$(byName("username")).should(disappear);
		$(byName("password")).should(disappear);
		currentUser = identifier;
	}


	private void logout() {
		logger.info("Logging out");
		$("#account-button").click();
		$(byTextCaseInsensitive("logout")).click();
	}

	@Override
	public void startProcess(String key, JsonNode values) {
		logger.info(String.format("Starting process %s%n", cssId(key)));
		open("/#/gears/processes/start");
		$(cssId(key)).click();
		if (values != null) submitFormAndWait(values);
	}

	@Override
	public void claimTask(String key, TaskExpression taskExpression) {
		// TODO: use parameters
		waitFor(waitMillis);
		if (urlMatch(".*/#/gears/tasks/.*")) return;
		open("/#/gears/tasks/group");
		$("#row_0").click();
	}

	private void waitFor(int milliseconds) {
		try {
			new WebDriverWait(WebDriverRunner.getWebDriver(), Duration.ofMillis(milliseconds)).until(arg -> false);
		} catch (TimeoutException ignored) {
		}
	}

	@Override
	public void basedOn(String value) {
		$(byText(value)).should(appear);
	}

	@Override
	public void submitTask(String processKey, TaskExpression taskExpression, JsonNode values) {
		waitFor(waitMillis);
		if (!urlMatch(".*/#/gears/tasks/.*")) {
			open("/#/gears/tasks/group");
			$("#row_0").click();
		}
		submitFormAndWait(values);
	}

	private void submitFormAndWait(JsonNode input) {
		String url = WebDriverRunner.url();
		submitForm(input);
		new WebDriverWait(WebDriverRunner.getWebDriver(), Duration.ofMillis(1000))
				.until(ExpectedConditions.not(ExpectedConditions.urlToBe(url)));
		waitFor(waitMillis);
	}

	private void submitForm(JsonNode input) {
		logger.info(String.format("Entering input from %s", input));
		input.fields().forEachRemaining(kvp -> {
			logger.info(String.format("For %s providing value %s", kvp.getKey(), kvp.getValue()));
			provideInput(kvp.getKey(), kvp.getValue());
		});
		$("#submit-form-button").click();
	}

	private void provideInput(String id, JsonNode value) {
		if (value.getNodeType() == JsonNodeType.ARRAY) {
			if (isMultipleTable($(cssId(id)).should(exist))) {
				provideMultipleTableInput(id, (ArrayNode)value);
			} else {
				provideMultipleInput(id, (ArrayNode)value);
			}
		} else {
			provideSingleInput(id, new InputValue(value));
		}
	}

	private void provideMultipleTableInput(String id, ArrayNode values) {
		int index = 0;
		SelenideElement table = $(cssId(id));
		for (var ite = values.elements(); ite.hasNext();) {
			if (!hasRow(table, index)) {
				table.find(String.format("#row-%d-add-button", index - 1)).click();
			}
			JsonNode currentRow = ite.next();
			int finalIndex = index;
			currentRow.fields().forEachRemaining(kvp -> {
				String inputId = String.format("%s[%d].%s", id, finalIndex, kvp.getKey());
				provideInput(inputId, kvp.getValue());
			});
			index++;
		}
	}

	private void provideSingleInput(String id, InputValue value) {
		SelenideElement element = $(cssId(id));
		if (value.text.equals(element.getAttribute("value")) || value.text.equals("")) return;
		if (isTextInput(element)) {
			element.setValue(value.text);
		} else if (isCombobox(element)) {
			String label = value.isLabel ? value.text : valueToLabel(value.text, id);
			if (label.equals(element.getAttribute("value"))) return;
			element.setValue(label);
			$$(withTextCaseInsensitive(label)).findBy(attribute("role", "option")).click();
		} else if (isDatePicker(element)) {
			element.setValue(dateTimeToInput(value.text, Objects.requireNonNull(element.getAttribute("placeholder"))));
		} else if (isTextArea(element)) {
			element.sendKeys(value.text);
		} else if (isFileInput(element)) {
			SelenideElement fileInput = element.find("[type='file']");
			fileInput.uploadFromClasspath("poetie.jpg");
		} else {
			provideRadioInput(element, value);
		}
	}

	private void provideMultipleInput(String id, ArrayNode values) {
		SelenideElement input = $(cssId(id));
		if (isCombobox(input)) {
			for (var ite = values.elements(); ite.hasNext();) {
				InputValue value = new InputValue(ite.next());
				String label = value.isLabel ? value.text : valueToLabel(value.text, id);
				if ($(withTextCaseInsensitive(label)).exists()) continue; // don't need to do anything if input is pre-filled
				input.setValue(label);
				$$(withTextCaseInsensitive(label)).findBy(attribute("role", "option")).click();
				$(withTextCaseInsensitive(label)).should(exist);
			}
			input.pressEscape();
		} else {
			for (var ite = values.elements(); ite.hasNext();) {
				provideRadioInput(input, new InputValue(ite.next()));
			}
		}
	}

	private void provideRadioInput(SelenideElement input, InputValue value) {
		WebElement button = value.isLabel ?
				input.findElement(withTextCaseInsensitive(value.text)) :
				input.findElement(byAttribute("value", value.text));
		if (button.getDomAttribute("checked") == null) button.click();
	}

	private String valueToLabel(String value, String fieldId) {
		String choicesId = getFormId();
		JsonNode result = client.invoke(Operation.choiceByValue(choicesId, fieldId, value, urlMatch(".*/#/gears/tasks/.*")));
		if (result.get("choice") == null) {
			throw new RuntimeException(String.format("Value %s not found for input %s", value, fieldId));
		}
		return result.get("choice").get("label").asText();
	}

	private static class InputValue {
		public final String text;
		public final boolean isLabel;

		public InputValue(JsonNode value) {
			if (value.getNodeType() == JsonNodeType.OBJECT) {
				JsonNode labelTry = value.get("label");
				if (labelTry != null) {
					text = labelTry.asText();
					isLabel = true;
				} else {
					JsonNode valueTry = value.get("value");
					if (valueTry != null) {
						text = valueTry.asText();
						isLabel = false;
					} else {
						throw new RuntimeException(String.format("Input is neither a label nor a value: %s", value));
					}
				}
			} else {
				text = value.asText();
				isLabel = false;
			}
		}
	}
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy