Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Testing for system diagrams #453

Merged
merged 4 commits into from
Jul 10, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ public enum Actrs implements Actor {
.description( "dependency" )
.tags( set( "abc", "ghi", "jkl", "mno" ) ) )
.call( a -> a
.from( Actrs.AVA ).to( Actrs.BEN )
.from( Actrs.AVA ).to( Actrs.CHE )
.request( REQ ).response( RES ) ) );

/**
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package com.mastercard.test.flow.report.index;

import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;

import com.mastercard.test.flow.report.seq.Browser;
Expand All @@ -16,4 +17,13 @@ class FileIndexTest extends AbstractIndexTest {
FileIndexTest() {
super( report.fileUrl() );
}

/**
* Shows that the interactions panel is not shown when we can't load the flow
* details
*/
@Test
void noInteractions() {
iseq.hasInteractionSummary( "" );
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@

import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;

import com.mastercard.test.flow.report.seq.Browser;
Expand All @@ -23,6 +24,61 @@ class ServedIndexTest extends AbstractIndexTest {
super( report.url() );
}

/**
* Checks that the interaction diagram for all flows is show as expected
*/
@Test
void interactions() {
iseq.hasInteractionSummary( "2 interactions between 3 actors" )
.expandInteractions()
.hasInteractions(
"Nodes:",
" AVA",
" BEN",
" CHE",
"Edges:",
" AVA normal solid BEN",
" AVA normal solid CHE" );
}

/**
* Checks that the interaction diagram for a filtered flow list is show as
* expected
*/
@Test
void filteredInteractions() {
iseq.clickTag( "PASS" )
.hasInteractionSummary( "1 interactions between 2 actors" )
.expandInteractions()
.hasInteractions(
"Nodes:",
" AVA",
" BEN",
" CHE",
"Edges:",
" AVA normal solid BEN",
" AVA thick solid CHE <INVISIBLE>" );
}

/**
* Checks that the interaction diagram highlights the hovered flow as expected
*/
@Test
void hoveredInteractions() {
iseq
.expandInteractions()
.hoverEntry( "basis [PASS, abc, def]" )
.hasInteractionSummary( "1 interactions between 2 actors" )
.hasInteractions(
"Nodes:",
" AVA",
" BEN",
" CHE",
"Edges:",
" AVA thick solid BEN",
" AVA normal dotted CHE" );
}

/**
* Checks that the expected log file has been created
* <p>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,18 +3,30 @@
import static java.time.Duration.ofSeconds;
import static java.util.stream.Collectors.joining;
import static java.util.stream.Collectors.toList;
import static java.util.stream.Collectors.toSet;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.openqa.selenium.support.ui.ExpectedConditions.elementToBeClickable;

import java.io.StringReader;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;
import java.util.stream.Stream;

import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.xpath.XPath;
import javax.xml.xpath.XPathConstants;
import javax.xml.xpath.XPathFactory;

import org.junit.jupiter.api.Assertions;
import org.openqa.selenium.By;
import org.openqa.selenium.WebElement;
import org.openqa.selenium.interactions.Actions;
import org.openqa.selenium.support.ui.WebDriverWait;
import org.w3c.dom.Document;
import org.w3c.dom.NodeList;
import org.xml.sax.InputSource;

/**
* Encapsulates the nuts and bolts of interacting with the index page so the
Expand Down Expand Up @@ -56,6 +68,42 @@ public IndexSequence index( String... arguments ) {
return get( url + args );
}

/**
* Hovers the pointer over an index entry
*
* @param name The text of the index item (same format as asserted by
* {@link #hasFlows(String...)})
* @return <code>this</code>
*/
public IndexSequence hoverEntry( String name ) {
trace( "hoverEntry", name );
List<WebElement> flowItems = driver.findElements( By.tagName( "app-flow-nav-item" ) );
int width = flowItems.stream()
.map( IndexSequence::flowDescription )
.mapToInt( String::length )
.max()
.orElse( 0 );
WebElement linkItem = flowItems.stream()
.filter( e -> name.equals( printFlow( width, e ) ) )
.findFirst()
.orElse( null );
if( linkItem == null ) {
Assertions.fail( String.format(
"Failed to find detail link '%s' in%s",
name,
flowItems.stream()
.map( e -> "\n" + printFlow( width, e ) )
.collect( joining() ) ) );
}
else {
new Actions( driver )
.moveToElement( linkItem )
.build()
.perform();
}
return self();
}

/**
* Navigates to a flow detail page by clicking on an index item
*
Expand Down Expand Up @@ -301,6 +349,117 @@ public IndexSequence dragToInclude( String tag ) {
return dragChip( tag, "tag_exclude", "tag_include" );
}

/**
* Asserts on the displayed interaction summary text.
*
* @param expected The expected text, or the empty string if we're expecting
* that element not to be shown at all
* @return <code>this</code>
*/
public IndexSequence hasInteractionSummary( String expected ) {
trace( "hasInteractionSummary", expected );
assertEquals( expected, driver
.findElements( By.id( "interaction_summary" ) ).stream()
.map( WebElement::getText )
.collect( joining( "\n" ) ),
"Interaction summary" );
return self();
}

/**
* Clicks on the expansion panel to show the interaction diagram.
*
* @return <code>this</code>
*/
public IndexSequence expandInteractions() {
trace( "expandInteractions" );
driver.findElement( By.id( "interactions_title" ) )
.click();
new WebDriverWait( driver, ofSeconds( 2 ) )
.until( elementToBeClickable( By.id( "interactions_diagram" ) ) );
return self();
}

/**
* Asserts on the displayed interaction diagram.
*
* @param expected The name of the resource file that contains the expected SVG
* content
* @return <code>this</code>
*/
public IndexSequence hasInteractions( String... expected ) {
trace( "hasInteractions", (Object[]) expected );
String svg = driver
.findElement( By.id( "interactions_diagram" ) )
.getAttribute( "innerHTML" );

String svgSummary = summariseSVG( svg );

assertEquals(
copypasta( expected ),
copypasta( svgSummary ),
"interaction diagram structure" );

return self();
}

/**
* Extracts the gist of the interaction diagram.
*
* @param svg The diagram
* @return A string that describes the diagram
*/
private static String summariseSVG( String svg ) {
try {
Document doc = DocumentBuilderFactory.newInstance()
.newDocumentBuilder()
.parse( new InputSource( new StringReader( svg ) ) );
XPath xpath = XPathFactory.newInstance().newXPath();

StringBuilder sb = new StringBuilder();

sb.append( "Nodes:" );
NodeList nl = (NodeList) xpath.compile( "//span[@class='nodeLabel']" )
.evaluate( doc, XPathConstants.NODESET );
for( int i = 0; i < nl.getLength(); i++ ) {
sb.append( "\n " )
.append( nl.item( i ).getTextContent() );
}

sb.append( "\nEdges:" );
nl = (NodeList) xpath.compile( "//path[@class!='arrowMarkerPath']" )
.evaluate( doc, XPathConstants.NODESET );
for( int i = 0; i < nl.getLength(); i++ ) {
Set<String> classes = Stream.of( nl.item( i )
.getAttributes()
.getNamedItem( "class" )
.getTextContent()
.split( " " ) )
.collect( toSet() );

sb.append( "\n " );
Stream.of( "LS-", "edge-thickness-", "edge-pattern-", "LE-" )
.forEach( prefix -> {
sb.append( " " )
.append( classes.stream()
.filter( c -> c.startsWith( prefix ) )
.map( c -> c.substring( prefix.length() ) )
.findFirst().orElse( prefix + "???" ) );
} );

if( nl.item( i ).getAttributes().getNamedItem( "style" ).getTextContent()
.contains( "stroke-width: 0" ) ) {
sb.append( " <INVISIBLE>" );
}
}

return sb.toString();
}
catch( Exception e ) {
throw new IllegalStateException( "failed to summarise\n" + svg, e );
}
}

private IndexSequence dragChip( String text, String sourceId, String destId ) {
WebElement source = driver.findElement( By.id( sourceId ) );
WebElement chip = source.findElements( By.tagName( "mat-chip" ) ).stream()
Expand Down Expand Up @@ -338,4 +497,5 @@ private static String printFlow( int descWidth, WebElement e ) {
String fmt = "%-" + descWidth + "s %s";
return String.format( fmt, flowDescription( e ), flowTags( e ) );
}

}
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
<mat-expansion-panel *ngIf="loadProgress != 0">
<mat-expansion-panel-header>
<mat-panel-title>Interactions</mat-panel-title>
<mat-panel-description>{{summary}}</mat-panel-description>
<mat-panel-title id="interactions_title">Interactions</mat-panel-title>
<mat-panel-description id="interaction_summary">{{summary}}</mat-panel-description>
</mat-expansion-panel-header>
<div #myTestDiv id="container">
<mat-progress-bar *ngIf="loadProgress != 100" [value]="loadProgress"></mat-progress-bar>
<pre class="mermaid"></pre>
<pre id="interactions_diagram" class="mermaid"></pre>
</div>
</mat-expansion-panel>