new test on audits; selenium upgrade to make usable by team hopefully

This commit is contained in:
2023-02-17 12:03:55 -06:00
parent 3eb831b30e
commit 32711098c1
18 changed files with 610 additions and 56 deletions

1
.gitignore vendored
View File

@ -18,6 +18,7 @@ yalc.lock
/build /build
/lib /lib
/target /target
/log
# misc # misc
.DS_Store .DS_Store

12
pom.xml
View File

@ -94,6 +94,18 @@
<version>20220924</version> <version>20220924</version>
<scope>test</scope> <scope>test</scope>
</dependency> </dependency>
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-api</artifactId>
<version>2.17.1</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-core</artifactId>
<version>2.17.1</version>
<scope>test</scope>
</dependency>
</dependencies> </dependencies>
<build> <build>

View File

@ -1,7 +1,7 @@
package com.kingsrook.qqq.materialdashbaord.lib; package com.kingsrook.qqq.materialdashboard.lib;
import com.kingsrook.qqq.materialdashbaord.lib.javalin.QSeleniumJavalin; import com.kingsrook.qqq.materialdashboard.lib.javalin.QSeleniumJavalin;
import io.github.bonigarcia.wdm.WebDriverManager; import io.github.bonigarcia.wdm.WebDriverManager;
import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.BeforeAll;

View File

@ -1,4 +1,4 @@
package com.kingsrook.qqq.materialdashbaord.lib; package com.kingsrook.qqq.materialdashboard.lib;
/******************************************************************************* /*******************************************************************************

View File

@ -1,4 +1,4 @@
package com.kingsrook.qqq.materialdashbaord.lib; package com.kingsrook.qqq.materialdashboard.lib;
import java.io.File; import java.io.File;
@ -6,6 +6,8 @@ import java.time.Duration;
import java.util.List; import java.util.List;
import java.util.Objects; import java.util.Objects;
import org.apache.commons.io.FileUtils; import org.apache.commons.io.FileUtils;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.openqa.selenium.By; import org.openqa.selenium.By;
import org.openqa.selenium.OutputType; import org.openqa.selenium.OutputType;
import org.openqa.selenium.StaleElementReferenceException; import org.openqa.selenium.StaleElementReferenceException;
@ -23,6 +25,8 @@ import static org.junit.jupiter.api.Assertions.fail;
*******************************************************************************/ *******************************************************************************/
public class QSeleniumLib public class QSeleniumLib
{ {
Logger LOG = LogManager.getLogger(QSeleniumLib.class);
public final WebDriver driver; public final WebDriver driver;
private long WAIT_SECONDS = 10; private long WAIT_SECONDS = 10;
@ -118,7 +122,7 @@ public class QSeleniumLib
{ {
// todo - if env says we're in CIRCLECI, then... just do a hard fail (or just not wait forever?) // todo - if env says we're in CIRCLECI, then... just do a hard fail (or just not wait forever?)
System.out.println("Going into a waitForever..."); LOG.warn("Going into a waitForever...");
new WebDriverWait(driver, Duration.ofHours(1)) new WebDriverWait(driver, Duration.ofHours(1))
.until(ExpectedConditions.presenceOfElementLocated(By.cssSelector(".wontEverBePresent"))); .until(ExpectedConditions.presenceOfElementLocated(By.cssSelector(".wontEverBePresent")));
} }
@ -131,13 +135,11 @@ public class QSeleniumLib
public void gotoAndWaitForBreadcrumbHeader(String path, String headerText) public void gotoAndWaitForBreadcrumbHeader(String path, String headerText)
{ {
driver.get(BASE_URL + path); driver.get(BASE_URL + path);
String title = driver.getTitle();
System.out.println("Page Title: " + title);
WebElement header = new WebDriverWait(driver, Duration.ofSeconds(WAIT_SECONDS)) WebElement header = new WebDriverWait(driver, Duration.ofSeconds(WAIT_SECONDS))
.until(ExpectedConditions.presenceOfElementLocated(By.cssSelector(QQQMaterialDashboardSelectors.BREADCRUMB_HEADER))); .until(ExpectedConditions.presenceOfElementLocated(By.cssSelector(QQQMaterialDashboardSelectors.BREADCRUMB_HEADER)));
System.out.println("Breadcrumb Header: " + header.getText()); LOG.debug("Navigated to [" + path + "]. Breadcrumb Header: " + header.getText());
assertEquals(headerText, header.getText()); assertEquals(headerText, header.getText());
} }
@ -158,7 +160,7 @@ public class QSeleniumLib
*******************************************************************************/ *******************************************************************************/
public List<WebElement> waitForSelectorAll(String cssSelector, int minCount) public List<WebElement> waitForSelectorAll(String cssSelector, int minCount)
{ {
System.out.println("Waiting for element matching selector [" + cssSelector + "]"); LOG.debug("Waiting for element matching selector [" + cssSelector + "]");
long start = System.currentTimeMillis(); long start = System.currentTimeMillis();
do do
@ -166,7 +168,7 @@ public class QSeleniumLib
List<WebElement> elements = driver.findElements(By.cssSelector(cssSelector)); List<WebElement> elements = driver.findElements(By.cssSelector(cssSelector));
if(elements.size() >= minCount) if(elements.size() >= minCount)
{ {
System.out.println("Found [" + elements.size() + "] element(s) matching selector [" + cssSelector + "]"); LOG.debug("Found [" + elements.size() + "] element(s) matching selector [" + cssSelector + "]");
return (elements); return (elements);
} }
@ -180,6 +182,32 @@ public class QSeleniumLib
/*******************************************************************************
**
*******************************************************************************/
public void waitForSelectorToNotExist(String cssSelector)
{
LOG.debug("Waiting for non-existence of element matching selector [" + cssSelector + "]");
long start = System.currentTimeMillis();
do
{
List<WebElement> elements = driver.findElements(By.cssSelector(cssSelector));
if(elements.size() == 0)
{
LOG.debug("Found non-existence of element(s) matching selector [" + cssSelector + "]");
return;
}
sleepABit();
}
while(start + (1000 * WAIT_SECONDS) > System.currentTimeMillis());
fail("Failed for non-existence of element matching selector [" + cssSelector + "] after [" + WAIT_SECONDS + "] seconds.");
}
/******************************************************************************* /*******************************************************************************
** **
*******************************************************************************/ *******************************************************************************/
@ -205,26 +233,51 @@ public class QSeleniumLib
/*******************************************************************************
**
*******************************************************************************/
public boolean waitForCondition(String message, Code<Boolean> c)
{
LOG.debug("Waiting for condition: " + message);
long start = System.currentTimeMillis();
do
{
Boolean b = c.run();
if(b != null && b)
{
LOG.debug("Condition became true: " + message);
return (true);
}
sleepABit();
}
while(start + (1000 * WAIT_SECONDS) > System.currentTimeMillis());
LOG.warn("Failed for condition to become true: " + message);
return (false);
}
/******************************************************************************* /*******************************************************************************
** **
*******************************************************************************/ *******************************************************************************/
public <T> T waitLoop(String message, Code<T> c) public <T> T waitLoop(String message, Code<T> c)
{ {
System.out.println("Waiting for: " + message); LOG.debug("Waiting for: " + message);
long start = System.currentTimeMillis(); long start = System.currentTimeMillis();
do do
{ {
T t = c.run(); T t = c.run();
if(t != null) if(t != null)
{ {
System.out.println("Found: " + message); LOG.debug("Found: " + message);
return (t); return (t);
} }
sleepABit(); sleepABit();
} }
while(start + (1000 * WAIT_SECONDS) > System.currentTimeMillis()); while(start + (1000 * WAIT_SECONDS) > System.currentTimeMillis());
System.out.println("Failed to match while waiting for: " + message); LOG.warn("Failed to match while waiting for: " + message);
return (null); return (null);
} }
@ -235,7 +288,7 @@ public class QSeleniumLib
*******************************************************************************/ *******************************************************************************/
public WebElement waitForSelectorContaining(String cssSelector, String textContains) public WebElement waitForSelectorContaining(String cssSelector, String textContains)
{ {
System.out.println("Waiting for element matching selector [" + cssSelector + "] containing text [" + textContains + "]."); LOG.debug("Waiting for element matching selector [" + cssSelector + "] containing text [" + textContains + "].");
long start = System.currentTimeMillis(); long start = System.currentTimeMillis();
do do
@ -247,7 +300,7 @@ public class QSeleniumLib
{ {
if(element.getText() != null && element.getText().toLowerCase().contains(textContains.toLowerCase())) if(element.getText() != null && element.getText().toLowerCase().contains(textContains.toLowerCase()))
{ {
System.out.println("Found element matching selector [" + cssSelector + "] containing text [" + textContains + "]."); LOG.debug("Found element matching selector [" + cssSelector + "] containing text [" + textContains + "].");
Actions actions = new Actions(driver); Actions actions = new Actions(driver);
actions.moveToElement(element); actions.moveToElement(element);
return (element); return (element);
@ -255,7 +308,7 @@ public class QSeleniumLib
} }
catch(StaleElementReferenceException sere) catch(StaleElementReferenceException sere)
{ {
System.err.println("Caught a StaleElementReferenceException - will retry."); LOG.debug("Caught a StaleElementReferenceException - will retry.");
} }
} }
@ -289,7 +342,7 @@ public class QSeleniumLib
} }
catch(StaleElementReferenceException sere) catch(StaleElementReferenceException sere)
{ {
System.err.println("Caught a StaleElementReferenceException - will retry."); LOG.debug("Caught a StaleElementReferenceException - will retry.");
} }
} }
return (null); return (null);
@ -329,11 +382,11 @@ public class QSeleniumLib
destFile.delete(); destFile.delete();
} }
FileUtils.moveFile(outputFile, destFile); FileUtils.moveFile(outputFile, destFile);
System.out.println("Made screenshot at: " + destFile); LOG.info("Made screenshot at: " + destFile);
} }
catch(Exception e) catch(Exception e)
{ {
System.err.println("Error taking screenshot to file: " + e.getMessage()); LOG.warn("Error taking screenshot to file: " + e.getMessage());
} }
} }
} }
@ -391,7 +444,7 @@ public class QSeleniumLib
{ {
if(i < noOfTries - 1) if(i < noOfTries - 1)
{ {
System.out.println("On try [" + i + " of " + noOfTries + "] caught: " + e.getMessage()); LOG.debug("On try [" + i + " of " + noOfTries + "] caught: " + e.getMessage());
} }
else else
{ {

View File

@ -1,4 +1,4 @@
package com.kingsrook.qqq.materialdashbaord.lib.javalin; package com.kingsrook.qqq.materialdashboard.lib.javalin;
import io.javalin.http.Context; import io.javalin.http.Context;

View File

@ -1,8 +1,10 @@
package com.kingsrook.qqq.materialdashbaord.lib.javalin; package com.kingsrook.qqq.materialdashboard.lib.javalin;
import io.javalin.http.Context; import io.javalin.http.Context;
import io.javalin.http.Handler; import io.javalin.http.Handler;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
/******************************************************************************* /*******************************************************************************
@ -11,6 +13,8 @@ import io.javalin.http.Handler;
*******************************************************************************/ *******************************************************************************/
public class CapturingHandler implements Handler public class CapturingHandler implements Handler
{ {
Logger LOG = LogManager.getLogger(CapturingHandler.class);
private final QSeleniumJavalin qSeleniumJavalin; private final QSeleniumJavalin qSeleniumJavalin;
@ -34,12 +38,12 @@ public class CapturingHandler implements Handler
{ {
if(qSeleniumJavalin.capturing) if(qSeleniumJavalin.capturing)
{ {
System.out.println("Capturing request for path [" + context.path() + "]"); LOG.info("Capturing request for path [" + context.path() + "]");
qSeleniumJavalin.captured.add(new CapturedContext(context)); qSeleniumJavalin.captured.add(new CapturedContext(context));
} }
else else
{ {
System.out.println("Not capturing request for path [" + context.path() + "]"); LOG.trace("Not capturing request for path [" + context.path() + "]");
} }
} }
} }

View File

@ -1,12 +1,14 @@
package com.kingsrook.qqq.materialdashbaord.lib.javalin; package com.kingsrook.qqq.materialdashboard.lib.javalin;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collections; import java.util.Collections;
import java.util.List; import java.util.List;
import com.kingsrook.qqq.materialdashbaord.lib.QSeleniumLib; import com.kingsrook.qqq.materialdashboard.lib.QSeleniumLib;
import io.javalin.Javalin; import io.javalin.Javalin;
import org.apache.commons.lang3.tuple.Pair; import org.apache.commons.lang3.tuple.Pair;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.eclipse.jetty.server.Connector; import org.eclipse.jetty.server.Connector;
import org.eclipse.jetty.server.HttpConnectionFactory; import org.eclipse.jetty.server.HttpConnectionFactory;
import static io.javalin.apibuilder.ApiBuilder.get; import static io.javalin.apibuilder.ApiBuilder.get;
@ -19,6 +21,8 @@ import static org.junit.jupiter.api.Assertions.fail;
*******************************************************************************/ *******************************************************************************/
public class QSeleniumJavalin public class QSeleniumJavalin
{ {
Logger LOG = LogManager.getLogger(QSeleniumJavalin.class);
private long WAIT_SECONDS = 10; private long WAIT_SECONDS = 10;
private List<Pair<String, String>> routesToFiles; private List<Pair<String, String>> routesToFiles;
@ -52,13 +56,13 @@ public class QSeleniumJavalin
** Fluent setter for routeToFile ** Fluent setter for routeToFile
** **
*******************************************************************************/ *******************************************************************************/
public QSeleniumJavalin withRouteToFile(String path, String file) public QSeleniumJavalin withRouteToFile(String path, String fixtureFilePath)
{ {
if(this.routesToFiles == null) if(this.routesToFiles == null)
{ {
this.routesToFiles = new ArrayList<>(); this.routesToFiles = new ArrayList<>();
} }
this.routesToFiles.add(Pair.of(path, file)); this.routesToFiles.add(Pair.of(path, fixtureFilePath));
return (this); return (this);
} }
@ -92,7 +96,7 @@ public class QSeleniumJavalin
{ {
for(Pair<String, String> routeToFile : routesToFiles) for(Pair<String, String> routeToFile : routesToFiles)
{ {
System.out.println("Setting up route for [" + routeToFile.getKey() + "] => [" + routeToFile.getValue() + "]"); LOG.debug("Setting up route for [" + routeToFile.getKey() + "] => [" + routeToFile.getValue() + "]");
get(routeToFile.getKey(), new RouteFromFileHandler(this, routeToFile)); get(routeToFile.getKey(), new RouteFromFileHandler(this, routeToFile));
post(routeToFile.getKey(), new RouteFromFileHandler(this, routeToFile)); post(routeToFile.getKey(), new RouteFromFileHandler(this, routeToFile));
} }
@ -105,7 +109,7 @@ public class QSeleniumJavalin
{ {
for(Pair<String, String> routeToString : routesToStrings) for(Pair<String, String> routeToString : routesToStrings)
{ {
System.out.println("Setting up route for [" + routeToString.getKey() + "] => [" + routeToString.getValue() + "]"); LOG.debug("Setting up route for [" + routeToString.getKey() + "] => [" + routeToString.getValue() + "]");
get(routeToString.getKey(), new RouteFromStringHandler(this, routeToString)); get(routeToString.getKey(), new RouteFromStringHandler(this, routeToString));
post(routeToString.getKey(), new RouteFromStringHandler(this, routeToString)); post(routeToString.getKey(), new RouteFromStringHandler(this, routeToString));
} }
@ -115,7 +119,7 @@ public class QSeleniumJavalin
javalin.before(new CapturingHandler(this)); javalin.before(new CapturingHandler(this));
javalin.error(404, context -> { javalin.error(404, context -> {
System.out.println("Returning 404 for [" + context.method() + " " + context.path() + "]"); LOG.warn("Returning 404 for [" + context.method() + " " + context.path() + "]");
pathsThat404ed.add(context.path()); pathsThat404ed.add(context.path());
}); });
@ -143,21 +147,33 @@ public class QSeleniumJavalin
if(javalin != null) if(javalin != null)
{ {
javalin.stop(); javalin.stop();
javalin = null;
} }
} }
/*******************************************************************************
**
*******************************************************************************/
public void restart()
{
stop();
start();
}
/******************************************************************************* /*******************************************************************************
** **
*******************************************************************************/ *******************************************************************************/
public void report() public void report()
{ {
System.out.println("Paths that 404'ed:"); LOG.info("Paths that 404'ed:");
pathsThat404ed.forEach(s -> System.out.println(" - " + s)); pathsThat404ed.forEach(s -> LOG.info(" - " + s));
System.out.println("Routes served as static files:"); LOG.info("Routes served as static files:");
routeFilesServed.forEach(s -> System.out.println(" - " + s)); routeFilesServed.forEach(s -> LOG.info(" - " + s));
} }
@ -167,7 +183,7 @@ public class QSeleniumJavalin
*******************************************************************************/ *******************************************************************************/
public void beginCapture() public void beginCapture()
{ {
System.out.println("Beginning to capture requests now"); LOG.info("Beginning to capture requests now");
capturing = true; capturing = true;
captured.clear(); captured.clear();
} }
@ -179,7 +195,7 @@ public class QSeleniumJavalin
*******************************************************************************/ *******************************************************************************/
public void endCapture() public void endCapture()
{ {
System.out.println("Ending capturing of requests now"); LOG.info("Ending capturing of requests now");
capturing = false; capturing = false;
} }
@ -200,17 +216,17 @@ public class QSeleniumJavalin
*******************************************************************************/ *******************************************************************************/
public CapturedContext waitForCapturedPath(String path) public CapturedContext waitForCapturedPath(String path)
{ {
System.out.println("Waiting for captured request for path [" + path + "]"); LOG.debug("Waiting for captured request for path [" + path + "]");
long start = System.currentTimeMillis(); long start = System.currentTimeMillis();
do do
{ {
// System.out.println(" captured paths: " + captured.stream().map(CapturedContext::getPath).collect(Collectors.joining(","))); // LOG.debug(" captured paths: " + captured.stream().map(CapturedContext::getPath).collect(Collectors.joining(",")));
for(CapturedContext context : captured) for(CapturedContext context : captured)
{ {
if(context.getPath().equals(path)) if(context.getPath().equals(path))
{ {
System.out.println("Found captured request for path [" + path + "]"); LOG.debug("Found captured request for path [" + path + "]");
return (context); return (context);
} }
} }
@ -230,19 +246,19 @@ public class QSeleniumJavalin
*******************************************************************************/ *******************************************************************************/
public CapturedContext waitForCapturedPathWithBodyContaining(String path, String bodyContaining) public CapturedContext waitForCapturedPathWithBodyContaining(String path, String bodyContaining)
{ {
System.out.println("Waiting for captured request for path [" + path + "] with body containing [" + bodyContaining + "]"); LOG.debug("Waiting for captured request for path [" + path + "] with body containing [" + bodyContaining + "]");
long start = System.currentTimeMillis(); long start = System.currentTimeMillis();
do do
{ {
// System.out.println(" captured paths: " + captured.stream().map(CapturedContext::getPath).collect(Collectors.joining(","))); // LOG.debug(" captured paths: " + captured.stream().map(CapturedContext::getPath).collect(Collectors.joining(",")));
for(CapturedContext context : captured) for(CapturedContext context : captured)
{ {
if(context.getPath().equals(path)) if(context.getPath().equals(path))
{ {
if(context.getBody() != null && context.getBody().contains(bodyContaining)) if(context.getBody() != null && context.getBody().contains(bodyContaining))
{ {
System.out.println("Found captured request for path [" + path + "] with body containing [" + bodyContaining + "]"); LOG.debug("Found captured request for path [" + path + "] with body containing [" + bodyContaining + "]");
return (context); return (context);
} }
} }

View File

@ -1,4 +1,4 @@
package com.kingsrook.qqq.materialdashbaord.lib.javalin; package com.kingsrook.qqq.materialdashboard.lib.javalin;
import java.nio.charset.StandardCharsets; import java.nio.charset.StandardCharsets;
@ -7,6 +7,8 @@ import io.javalin.http.Context;
import io.javalin.http.Handler; import io.javalin.http.Handler;
import org.apache.commons.io.IOUtils; import org.apache.commons.io.IOUtils;
import org.apache.commons.lang3.tuple.Pair; import org.apache.commons.lang3.tuple.Pair;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
/******************************************************************************* /*******************************************************************************
@ -14,6 +16,8 @@ import org.apache.commons.lang3.tuple.Pair;
*******************************************************************************/ *******************************************************************************/
public class RouteFromFileHandler implements Handler public class RouteFromFileHandler implements Handler
{ {
Logger LOG = LogManager.getLogger(RouteFromFileHandler.class);
private final String route; private final String route;
private final String filePath; private final String filePath;
private final QSeleniumJavalin qSeleniumJavalin; private final QSeleniumJavalin qSeleniumJavalin;
@ -42,7 +46,7 @@ public class RouteFromFileHandler implements Handler
try try
{ {
qSeleniumJavalin.routeFilesServed.add(this.route); qSeleniumJavalin.routeFilesServed.add(this.route);
System.out.println("Serving route [" + this.route + "] via file [" + this.filePath + "]"); LOG.debug("Serving route [" + this.route + "] via file [" + this.filePath + "]");
List<String> lines = IOUtils.readLines(getClass().getResourceAsStream("/fixtures/" + this.filePath), StandardCharsets.UTF_8); List<String> lines = IOUtils.readLines(getClass().getResourceAsStream("/fixtures/" + this.filePath), StandardCharsets.UTF_8);
context.result(String.join("\n", lines)); context.result(String.join("\n", lines));
} }

View File

@ -1,9 +1,11 @@
package com.kingsrook.qqq.materialdashbaord.lib.javalin; package com.kingsrook.qqq.materialdashboard.lib.javalin;
import io.javalin.http.Context; import io.javalin.http.Context;
import io.javalin.http.Handler; import io.javalin.http.Handler;
import org.apache.commons.lang3.tuple.Pair; import org.apache.commons.lang3.tuple.Pair;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
/******************************************************************************* /*******************************************************************************
@ -11,6 +13,8 @@ import org.apache.commons.lang3.tuple.Pair;
*******************************************************************************/ *******************************************************************************/
public class RouteFromStringHandler implements Handler public class RouteFromStringHandler implements Handler
{ {
Logger LOG = LogManager.getLogger(RouteFromStringHandler.class);
private final String route; private final String route;
private final String responseString; private final String responseString;
private final QSeleniumJavalin qSeleniumJavalin; private final QSeleniumJavalin qSeleniumJavalin;
@ -37,7 +41,7 @@ public class RouteFromStringHandler implements Handler
public void handle(Context context) public void handle(Context context)
{ {
qSeleniumJavalin.routeFilesServed.add(this.route); qSeleniumJavalin.routeFilesServed.add(this.route);
System.out.println("Serving route [" + this.route + "] via static String"); LOG.debug("Serving route [" + this.route + "] via static String");
context.result(this.responseString); context.result(this.responseString);
} }
} }

View File

@ -19,12 +19,12 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>. * along with this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
package com.kingsrook.qqq.materialdashbaord.tests; package com.kingsrook.qqq.materialdashboard.tests;
import com.kingsrook.qqq.materialdashbaord.lib.QBaseSeleniumTest; import com.kingsrook.qqq.materialdashboard.lib.QBaseSeleniumTest;
import com.kingsrook.qqq.materialdashbaord.lib.QQQMaterialDashboardSelectors; import com.kingsrook.qqq.materialdashboard.lib.QQQMaterialDashboardSelectors;
import com.kingsrook.qqq.materialdashbaord.lib.javalin.QSeleniumJavalin; import com.kingsrook.qqq.materialdashboard.lib.javalin.QSeleniumJavalin;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;

View File

@ -0,0 +1,160 @@
/*
* QQQ - Low-code Application Framework for Engineers.
* Copyright (C) 2021-2022. Kingsrook, LLC
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
* contact@kingsrook.com
* https://github.com/Kingsrook/
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.kingsrook.qqq.materialdashboard.tests;
import java.util.List;
import com.kingsrook.qqq.materialdashboard.lib.QBaseSeleniumTest;
import com.kingsrook.qqq.materialdashboard.lib.javalin.CapturedContext;
import com.kingsrook.qqq.materialdashboard.lib.javalin.QSeleniumJavalin;
import org.junit.jupiter.api.Test;
import org.openqa.selenium.WebElement;
import static org.assertj.core.api.Assertions.assertThat;
import static org.junit.jupiter.api.Assertions.assertEquals;
/*******************************************************************************
** Test for the audit screen (e.g., modal)
*******************************************************************************/
public class AuditTest extends QBaseSeleniumTest
{
/*******************************************************************************
**
*******************************************************************************/
@Override
protected void addJavalinRoutes(QSeleniumJavalin qSeleniumJavalin)
{
super.addJavalinRoutes(qSeleniumJavalin);
qSeleniumJavalin
.withRouteToFile("/data/person/1701", "data/person/1701.json");
}
/*******************************************************************************
**
*******************************************************************************/
@Test
void testOpenAuditsFromRecordWithNoAuditsFoundThenClose()
{
/////////////////////////////////////////////////////////////////////
// setup route for empty audits - then assert we show such message //
/////////////////////////////////////////////////////////////////////
qSeleniumJavalin.withRouteToFile("/data/audit/query", "data/audit/query-empty.json");
qSeleniumJavalin.restart();
qSeleniumLib.gotoAndWaitForBreadcrumbHeader("/peopleApp/greetingsApp/person/1701", "John Doe");
qSeleniumLib.waitForSelectorContaining("BUTTON", "Actions").click();
qSeleniumLib.waitForSelectorContaining("LI", "Audit").click();
qSeleniumLib.waitForSelector(".audit");
qSeleniumLib.waitForSelectorContaining("DIV", "Audit for Person: John Doe");
qSeleniumLib.waitForSelectorContaining("DIV", "No audits were found for this record");
///////////////////////////////////////
// make sure we can close the dialog //
///////////////////////////////////////
qSeleniumLib.waitForSelectorContaining("BUTTON", "Close").click();
qSeleniumLib.waitForSelectorToNotExist(".audit");
qSeleniumLib.takeScreenshotToFile();
}
/*******************************************************************************
**
*******************************************************************************/
@Test
void testOpenAuditsFromRecordWithSomeAuditsFound()
{
String auditQueryPath = "/data/audit/query";
qSeleniumJavalin.withRouteToFile(auditQueryPath, "data/audit/query.json");
qSeleniumJavalin.restart();
qSeleniumLib.gotoAndWaitForBreadcrumbHeader("/peopleApp/greetingsApp/person/1701", "John Doe");
qSeleniumLib.waitForSelectorContaining("BUTTON", "Actions").click();
qSeleniumLib.waitForSelectorContaining("LI", "Audit").click();
qSeleniumLib.waitForSelectorContaining("DIV", "Audit for Person: John Doe");
qSeleniumLib.waitForSelectorContaining("DIV", "Showing all 5 audits for this record");
//////////////////////////////////////////////////////////////////////////////////////////////////
// assertions about the different styles of detail messages (set a value, cleared a value, etc) //
//////////////////////////////////////////////////////////////////////////////////////////////////
qSeleniumLib.waitForSelectorContaining("LI", "First Name: Set to John");
qSeleniumLib.waitForSelectorContaining("B", "John");
qSeleniumLib.waitForSelectorContaining("LI", "Last Name: Removed value Doe");
qSeleniumLib.waitForSelectorContaining("LI", "clientId: Changed from BetaMax to ACME");
qSeleniumLib.waitForSelectorContaining("B", "ACME");
qSeleniumLib.waitForSelectorContaining("DIV", "Audit message here");
qSeleniumLib.waitForSelectorContaining("LI", "This is a detail message");
}
/*******************************************************************************
**
*******************************************************************************/
@Test
void testOpenAuditsFromRecordReSortList()
{
String auditQueryPath = "/data/audit/query";
qSeleniumJavalin.withRouteToFile(auditQueryPath, "data/audit/query.json");
qSeleniumJavalin.restart();
qSeleniumLib.gotoAndWaitForBreadcrumbHeader("/peopleApp/greetingsApp/person/1701", "John Doe");
qSeleniumLib.waitForSelectorContaining("BUTTON", "Actions").click();
qSeleniumLib.waitForSelectorContaining("LI", "Audit").click();
qSeleniumLib.waitForSelectorContaining("DIV", "Audit for Person: John Doe");
/////////////////////////////////////////////////////////////////////////////////////////
// make sure clicking the re-sort buttons works (fires a new request w/ opposite sort) //
/////////////////////////////////////////////////////////////////////////////////////////
qSeleniumJavalin.beginCapture();
WebElement sortAscButton = qSeleniumLib.waitForSelectorContaining("BUTTON", "arrow_upward");
assertEquals("false", sortAscButton.getAttribute("aria-pressed"));
sortAscButton.click();
qSeleniumJavalin.waitForCapturedPath(auditQueryPath);
qSeleniumJavalin.endCapture();
List<CapturedContext> captured = qSeleniumJavalin.getCaptured();
captured = captured.stream().filter(cc -> cc.getPath().equals(auditQueryPath)).toList();
assertEquals(1, captured.size());
assertThat(captured.get(0).getBody()).contains("\"isAscending\":true");
sortAscButton = qSeleniumLib.waitForSelectorContaining("BUTTON", "arrow_upward");
assertEquals("true", sortAscButton.getAttribute("aria-pressed"));
qSeleniumJavalin.beginCapture();
qSeleniumLib.waitForSelectorContaining("BUTTON", "arrow_downward").click();
qSeleniumJavalin.waitForCapturedPath(auditQueryPath);
qSeleniumJavalin.endCapture();
captured = qSeleniumJavalin.getCaptured();
captured = captured.stream().filter(cc -> cc.getPath().equals(auditQueryPath)).toList();
assertEquals(1, captured.size());
assertThat(captured.get(0).getBody()).contains("\"isAscending\":false");
qSeleniumLib.takeScreenshotToFile();
}
}

View File

@ -19,13 +19,13 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>. * along with this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
package com.kingsrook.qqq.materialdashbaord.tests; package com.kingsrook.qqq.materialdashboard.tests;
import com.kingsrook.qqq.materialdashbaord.lib.QBaseSeleniumTest; import com.kingsrook.qqq.materialdashboard.lib.QBaseSeleniumTest;
import com.kingsrook.qqq.materialdashbaord.lib.QQQMaterialDashboardSelectors; import com.kingsrook.qqq.materialdashboard.lib.QQQMaterialDashboardSelectors;
import com.kingsrook.qqq.materialdashbaord.lib.javalin.CapturedContext; import com.kingsrook.qqq.materialdashboard.lib.javalin.CapturedContext;
import com.kingsrook.qqq.materialdashbaord.lib.javalin.QSeleniumJavalin; import com.kingsrook.qqq.materialdashboard.lib.javalin.QSeleniumJavalin;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import org.openqa.selenium.By; import org.openqa.selenium.By;
import org.openqa.selenium.WebElement; import org.openqa.selenium.WebElement;

View File

@ -0,0 +1,3 @@
{
"records": []
}

View File

@ -0,0 +1,245 @@
{
"records": [
{
"tableName": "audit",
"recordLabel": "Parcel 1191682",
"values": {
"id": 623577,
"auditTableId": 4,
"auditUserId": 2,
"recordId": 1191682,
"message": "Record was Inserted",
"timestamp": "2023-02-17T14:11:16Z",
"clientId": 107,
"auditDetail.id": 278660,
"auditDetail.auditId": 623577,
"auditDetail.message": "Set First Name to John",
"auditDetail.fieldName": "firstName",
"auditDetail.newValue": "John"
},
"displayValues": {
"auditTableId": "Parcel",
"auditUserId": "QQQ User",
"clientId": "ACME",
"id": "623577",
"recordId": "1191682",
"message": "Record was Inserted",
"timestamp": "2023-02-17T14:11:16Z"
}
},
{
"tableName": "audit",
"recordLabel": "Parcel 1191682",
"values": {
"id": 623577,
"auditTableId": 4,
"auditUserId": 2,
"recordId": 1191682,
"message": "Record was Inserted",
"timestamp": "2023-02-17T14:11:16Z",
"clientId": 107,
"auditDetail.id": 278661,
"auditDetail.auditId": 623577,
"auditDetail.message": "Removed Doe from Last Name",
"auditDetail.fieldName": "lastName",
"auditDetail.oldValue": "Doe"
},
"displayValues": {
"auditTableId": "Parcel",
"auditUserId": "QQQ User",
"clientId": "ACME",
"id": "623577",
"recordId": "1191682",
"message": "Record was Inserted",
"timestamp": "2023-02-17T14:11:16Z"
}
},
{
"tableName": "audit",
"recordLabel": "Parcel 1191682",
"values": {
"id": 623577,
"auditTableId": 4,
"auditUserId": 2,
"recordId": 1191682,
"message": "Record was Inserted",
"timestamp": "2023-02-17T14:11:16Z",
"clientId": 107,
"auditDetail.id": 278662,
"auditDetail.auditId": 623577,
"auditDetail.message": "Set Client to ACME",
"auditDetail.fieldName": "clientId",
"auditDetail.oldValue": "BetaMax",
"auditDetail.newValue": "ACME"
},
"displayValues": {
"auditTableId": "Parcel",
"auditUserId": "QQQ User",
"clientId": "ACME",
"id": "623577",
"recordId": "1191682",
"message": "Record was Inserted",
"timestamp": "2023-02-17T14:11:16Z"
}
},
{
"tableName": "audit",
"recordLabel": "Parcel 1191682",
"values": {
"id": 624804,
"auditTableId": 4,
"auditUserId": 2,
"recordId": 1191682,
"message": "Record was Edited",
"timestamp": "2023-02-17T14:13:16Z",
"clientId": 107,
"auditDetail.id": 278990,
"auditDetail.auditId": 624804,
"auditDetail.message": "Set SLA Expected Service Days to 2",
"auditDetail.fieldName": "slaExpectedServiceDays",
"auditDetail.newValue": "2"
},
"displayValues": {
"auditTableId": "Parcel",
"auditUserId": "QQQ User",
"clientId": "ACME",
"id": "624804",
"recordId": "1191682",
"message": "Record was Edited",
"timestamp": "2023-02-17T14:13:16Z"
}
},
{
"tableName": "audit",
"recordLabel": "Parcel 1191682",
"values": {
"id": 624804,
"auditTableId": 4,
"auditUserId": 2,
"recordId": 1191682,
"message": "Record was Edited",
"timestamp": "2023-02-17T14:13:16Z",
"clientId": 107,
"auditDetail.id": 278991,
"auditDetail.auditId": 624804,
"auditDetail.message": "Set SLA Status to \"Pending\"",
"auditDetail.fieldName": "slaStatusId",
"auditDetail.newValue": "Pending"
},
"displayValues": {
"auditTableId": "Parcel",
"auditUserId": "QQQ User",
"clientId": "ACME",
"id": "624804",
"recordId": "1191682",
"message": "Record was Edited",
"timestamp": "2023-02-17T14:13:16Z"
}
},
{
"tableName": "audit",
"recordLabel": "Parcel 1191682",
"values": {
"id": 624809,
"auditTableId": 4,
"auditUserId": 2,
"recordId": 1191682,
"message": "Audit message here",
"timestamp": "2023-02-17T14:13:16Z",
"clientId": 107,
"auditDetail.id": 279000,
"auditDetail.auditId": 624809,
"auditDetail.message": "This is a detail message"
},
"displayValues": {
"auditTableId": "Parcel",
"auditUserId": "QQQ User",
"clientId": "ACME",
"id": "624809",
"recordId": "1191682",
"message": "Audit message here",
"timestamp": "2023-02-17T14:13:16Z"
}
},
{
"tableName": "audit",
"recordLabel": "Parcel 1191682",
"values": {
"id": 737694,
"auditTableId": 4,
"auditUserId": 2,
"recordId": 1191682,
"message": "Record was Edited",
"timestamp": "2023-02-17T17:22:08Z",
"clientId": 107,
"auditDetail.id": 299222,
"auditDetail.auditId": 737694,
"auditDetail.message": "Set Estimated Delivery Date Time to 2023-02-18 07:00:00 PM EST",
"auditDetail.fieldName": "estimatedDeliveryDateTime",
"auditDetail.newValue": "2023-02-18 07:00:00 PM EST"
},
"displayValues": {
"auditTableId": "Parcel",
"auditUserId": "QQQ User",
"clientId": "ACME",
"id": "737694",
"recordId": "1191682",
"message": "Record was Edited",
"timestamp": "2023-02-17T17:22:08Z"
}
},
{
"tableName": "audit",
"recordLabel": "Parcel 1191682",
"values": {
"id": 737694,
"auditTableId": 4,
"auditUserId": 2,
"recordId": 1191682,
"message": "Record was Edited",
"timestamp": "2023-02-17T17:22:08Z",
"clientId": 107,
"auditDetail.id": 299223,
"auditDetail.auditId": 737694,
"auditDetail.message": "Changed Parcel Tracking Status from \"Unknown\" to \"Pre Transit\"",
"auditDetail.fieldName": "parcelTrackingStatusId",
"auditDetail.oldValue": "Unknown",
"auditDetail.newValue": "Pre Transit"
},
"displayValues": {
"auditTableId": "Parcel",
"auditUserId": "QQQ User",
"clientId": "ACME",
"id": "737694",
"recordId": "1191682",
"message": "Record was Edited",
"timestamp": "2023-02-17T17:22:08Z"
}
},
{
"tableName": "audit",
"recordLabel": "Parcel 1191682",
"values": {
"id": 737695,
"auditTableId": 4,
"auditUserId": 2,
"recordId": 1191682,
"message": "Updating Parcel based on updated tracking details",
"timestamp": "2023-02-17T17:22:09Z",
"clientId": 107,
"auditDetail.id": 299224,
"auditDetail.auditId": 737695,
"auditDetail.message": "Set Parcel Tracking Status to Pre Transit based on most recent tracking update: Shipment information sent to FedEx"
},
"displayValues": {
"auditTableId": "Parcel",
"auditUserId": "QQQ User",
"clientId": "ACME",
"id": "737695",
"recordId": "1191682",
"message": "Updating Parcel based on updated tracking details",
"timestamp": "2023-02-17T17:22:09Z"
}
}
]
}

View File

@ -0,0 +1,16 @@
{
"tableName": "person",
"recordLabel": "John Doe",
"values": {
"name": "John Doe",
"id": 1710,
"createDate": "2022-08-30T00:31:00Z",
"modifyDate": "2022-08-30T00:31:00Z"
},
"displayValues": {
"name": "John Doe",
"id": 1710,
"createDate": "2022-08-30T00:31:00Z",
"modifyDate": "2022-08-30T00:31:00Z"
}
}

View File

@ -53,6 +53,21 @@
"TABLE_INSERT", "TABLE_INSERT",
"TABLE_DELETE" "TABLE_DELETE"
] ]
},
"audit": {
"name": "audit",
"label": "Audits",
"isHidden": true,
"iconName": "location_city",
"deletePermission": false,
"editPermission": false,
"insertPermission": false,
"readPermission": true,
"capabilities": [
"TABLE_COUNT",
"TABLE_GET",
"TABLE_QUERY"
]
} }
}, },
"processes": { "processes": {

View File

@ -0,0 +1,21 @@
<?xml version="1.0" encoding="UTF-8"?>
<Configuration>
<Appenders>
<Console name="SystemOutAppender" target="SYSTEM_OUT">
<LevelRangeFilter minLevel="ERROR" maxLevel="DEBUG" onMatch="ACCEPT" onMismatch="DENY"/>
<PatternLayout pattern="%highlight{%date{ISO8601} | %level | %threadName | %logger{1} | %message%n}"/>
</Console>
<File name="LogFileAppender" fileName="log/qqq.log">
<LevelRangeFilter minLevel="ERROR" maxLevel="all" onMatch="ACCEPT" onMismatch="DENY"/>
<PatternLayout pattern="%date{ISO8601} | %relative | %level | %threadName | %logger{1} | %message%n"/>
</File>
</Appenders>
<Loggers>
<Logger name="org.apache.log4j.xml" additivity="false">
</Logger>
<Root level="all">
<AppenderRef ref="SystemOutAppender"/>
<!-- <AppenderRef ref="LogFileAppender"/> -->
</Root>
</Loggers>
</Configuration>