Skip to content

Commit 260da87

Browse files
authored
Merge pull request #481 from TESTARtool/statemodel_dev
State model updates and fixes
2 parents 0057fde + 897661a commit 260da87

22 files changed

Lines changed: 351 additions & 57 deletions

File tree

CHANGELOG

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,11 @@
1+
#TESTAR v2.8.12 (1-Jun-2026)
2+
- Add executeTriggeredAction in GenericUtilsProtocol
3+
- Update parabank protocol to save login sequence in the state model
4+
- Set Tag.InputText to action and concrete action
5+
- Add screenshot to ConcreteAction
6+
- Update web isFullVisibleAtCanvasBrowser logic
7+
8+
19
#TESTAR v2.8.11 (25-May-2026)
210
- Bump org.seleniumhq.selenium:selenium-java from 4.43.0 to 4.44.0
311
- Update devtools dependencies to v148

VERSION

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
2.8.11
1+
2.8.12

core/src/org/testar/monkey/alayer/Tags.java

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -144,6 +144,9 @@ private Tags() {}
144144
/** Usually attached to an object of {@link State}. The value is a screenshot of the state. */
145145
public static final Tag<String> ScreenshotPath = from("ScreenshotPath", String.class);
146146

147+
/** Usually attached to an object of {@link Action}. The value is a screenshot of the action target area. */
148+
public static final Tag<String> ActionScreenshotPath = from("ActionScreenshotPath", String.class);
149+
147150
/** Usually attached to a {@link State} object. The value is a list of outcomes of test oracles for that state. */
148151
@SuppressWarnings("unchecked")
149152
public static final Tag<List<Verdict>> OracleVerdicts = from("OracleVerdicts", (Class<List<Verdict>>)(Class<?>)List.class);

core/src/org/testar/monkey/alayer/actions/AnnotatingActionCompiler.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -166,6 +166,7 @@ public Action clickTypeInto(final Widget widget, double relX, double relY, final
166166
//ret.set(Tags.Desc, "Type '" + Util.abbreviate(text, 5, "...") + "' into '" + widget.get(Tags.Desc, "<no description>" + "'"));
167167
ret.set(Tags.Desc, "Type '" + Util.abbreviate(text, DISPLAY_TEXT_MAX_LENGTH, "...") + "' into '" + widget.get(Tags.Desc, "<no description>" + "'")); // by urueda
168168
ret.mapOriginWidget(widget);
169+
ret.set(Tags.InputText, text);
169170
return ret;
170171
}
171172

@@ -176,6 +177,7 @@ public Action clickAndReplaceText(final Position position, final String text){
176177
ret.set(Tags.Visualizer, new TextVisualizer(position, Util.abbreviate(text, DISPLAY_TEXT_MAX_LENGTH, "..."), TypePen));
177178
//ret.set(Tags.Desc, "Type '" + Util.abbreviate(text, 5, "...") + "' into '" + position.toString() + "'");
178179
ret.set(Tags.Desc, "Replace '" + Util.abbreviate(text, DISPLAY_TEXT_MAX_LENGTH, "...") + "' into '" + position.toString() + "'");
180+
ret.set(Tags.InputText, text);
179181
ret.set(Tags.Role, ActionRoles.ClickTypeInto);
180182
return ret;
181183
}
@@ -187,6 +189,7 @@ public Action clickAndAppendText(final Position position, final String text){
187189
ret.set(Tags.Visualizer, new TextVisualizer(position, Util.abbreviate(text, DISPLAY_TEXT_MAX_LENGTH, "..."), TypePen));
188190
//ret.set(Tags.Desc, "Type '" + Util.abbreviate(text, 5, "...") + "' into '" + position.toString() + "'");
189191
ret.set(Tags.Desc, "Append '" + Util.abbreviate(text, DISPLAY_TEXT_MAX_LENGTH, "...") + "' into '" + position.toString() + "'");
192+
ret.set(Tags.InputText, text);
190193
ret.set(Tags.Role, ActionRoles.ClickTypeInto);
191194
return ret;
192195
}
@@ -197,6 +200,7 @@ public Action pasteAndReplaceText(final Position position, final String text){
197200
ret.set(Tags.Visualizer, new TextVisualizer(position, Util.abbreviate(text, DISPLAY_TEXT_MAX_LENGTH, "..."), TypePen));
198201
ret.set(Tags.Role, ActionRoles.PasteTextInto);
199202
ret.set(Tags.Desc, "Paste Text: " + StringEscapeUtils.escapeHtml4(text));
203+
ret.set(Tags.InputText, text);
200204
return ret;
201205
}
202206

@@ -206,6 +210,7 @@ public Action pasteAndAppendText(final Position position, final String text){
206210
ret.set(Tags.Visualizer, new TextVisualizer(position, Util.abbreviate(text, DISPLAY_TEXT_MAX_LENGTH, "..."), TypePen));
207211
ret.set(Tags.Role, ActionRoles.PasteTextInto);
208212
ret.set(Tags.Desc, "Append Paste Text: " + StringEscapeUtils.escapeHtml4(text));
213+
ret.set(Tags.InputText, text);
209214
return ret;
210215
}
211216

statemodel/resources/graphs/graph.jsp

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -831,6 +831,20 @@
831831
contentPanelHeader.appendChild(closeButton);
832832
833833
if (targetEdge.hasClass('ConcreteAction')) {
834+
// add the concrete action screenshot in the side panel
835+
let actionPopupAnchor = document.createElement("a");
836+
actionPopupAnchor.href = "${contentFolder}/" + targetEdge.id() + ".png";
837+
$(actionPopupAnchor).magnificPopup(
838+
{type: "image"}
839+
);
840+
841+
let actionImage = document.createElement("img");
842+
actionImage.alt = "Image for edge " + targetEdge.id();
843+
actionImage.src = "${contentFolder}/" + targetEdge.id() + ".png";
844+
actionImage.classList.add("node-img-full");
845+
actionPopupAnchor.appendChild(actionImage);
846+
contentPanel.appendChild(actionPopupAnchor);
847+
834848
// if it is a concrete action edge, we add a popup
835849
// first the content
836850
let popupContent = document.createElement("div");
@@ -849,13 +863,20 @@
849863
targetDiv.classList.add('screenshot');
850864
targetDiv.appendChild(targetImg);
851865
866+
let actionDiv = document.createElement("div");
867+
let actionImg = document.createElement("img");
868+
actionImg.src = "${contentFolder}/" + targetEdge.id() + ".png";
869+
actionDiv.classList.add('screenshot');
870+
actionDiv.appendChild(actionImg);
871+
852872
// add the edge text
853873
let descDiv = document.createElement("div");
854874
descDiv.appendChild(document.createTextNode(targetEdge.data('Desc')));
855875
descDiv.classList.add('action');
856876
857877
// add the divs in order
858878
popupContent.appendChild(sourceDiv);
879+
popupContent.appendChild(actionDiv);
859880
popupContent.appendChild(descDiv);
860881
popupContent.appendChild(targetDiv);
861882
contentPanelHeader.appendChild(popupContent);

statemodel/src/org/testar/statemodel/ConcreteAction.java

Lines changed: 22 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
/***************************************************************************************************
22
*
3-
* Copyright (c) 2018 - 2025 Open Universiteit - www.ou.nl
4-
* Copyright (c) 2018 - 2025 Universitat Politecnica de Valencia - www.upv.es
3+
* Copyright (c) 2018 - 2026 Open Universiteit - www.ou.nl
4+
* Copyright (c) 2018 - 2026 Universitat Politecnica de Valencia - www.upv.es
55
*
66
* Redistribution and use in source and binary forms, with or without
77
* modification, are permitted provided that the following conditions are met:
@@ -30,6 +30,7 @@
3030

3131
package org.testar.statemodel;
3232

33+
import java.util.Arrays;
3334
import java.util.Objects;
3435

3536
public class ConcreteAction extends ModelWidget {
@@ -44,6 +45,9 @@ public class ConcreteAction extends ModelWidget {
4445
*/
4546
private final AbstractAction abstractAction;
4647

48+
// a byte array holding the screenshot for this action
49+
private byte[] screenshot;
50+
4751
/**
4852
* Constructor.
4953
* @param actionId
@@ -64,4 +68,20 @@ public String getActionId() {
6468
public AbstractAction getAbstractAction() {
6569
return abstractAction;
6670
}
71+
72+
/**
73+
* Retrieves the screenshot data for this action.
74+
* @return
75+
*/
76+
public byte[] getScreenshot() {
77+
return screenshot != null ? Arrays.copyOf(screenshot, screenshot.length) : new byte[0];
78+
}
79+
80+
/**
81+
* Sets the screenshot data for this action.
82+
* @param screenshot
83+
*/
84+
public void setScreenshot(byte[] screenshot) {
85+
this.screenshot = screenshot != null ? Arrays.copyOf(screenshot, screenshot.length) : null;
86+
}
6787
}

statemodel/src/org/testar/statemodel/ConcreteActionFactory.java

Lines changed: 41 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
/***************************************************************************************************
22
*
3-
* Copyright (c) 2018 - 2025 Open Universiteit - www.ou.nl
4-
* Copyright (c) 2018 - 2025 Universitat Politecnica de Valencia - www.upv.es
3+
* Copyright (c) 2018 - 2026 Open Universiteit - www.ou.nl
4+
* Copyright (c) 2018 - 2026 Universitat Politecnica de Valencia - www.upv.es
55
*
66
* Redistribution and use in source and binary forms, with or without
77
* modification, are permitted provided that the following conditions are met:
@@ -30,13 +30,24 @@
3030

3131
package org.testar.statemodel;
3232

33+
import org.apache.logging.log4j.Level;
34+
import org.apache.logging.log4j.LogManager;
35+
import org.apache.logging.log4j.Logger;
36+
import org.testar.monkey.Util;
3337
import org.testar.monkey.alayer.Action;
3438
import org.testar.monkey.alayer.Tag;
3539
import org.testar.monkey.alayer.Tags;
3640
import org.testar.monkey.alayer.Widget;
3741

42+
import java.io.IOException;
43+
import java.nio.file.Files;
44+
import java.nio.file.Path;
45+
import java.nio.file.Paths;
46+
3847
public class ConcreteActionFactory {
3948

49+
protected static final Logger logger = LogManager.getLogger();
50+
4051
public static ConcreteAction createConcreteAction(Action action, AbstractAction abstractAction) {
4152
ConcreteAction concreteAction = new ConcreteAction(action.get(Tags.ConcreteID), abstractAction);
4253

@@ -51,6 +62,34 @@ public static ConcreteAction createConcreteAction(Action action, AbstractAction
5162
if(action.get(Tags.Desc, null) != null)
5263
setSpecificAttribute(concreteAction, Tags.Desc, action.get(Tags.Desc));
5364

65+
// check if the action as attached an InputText
66+
// if so, set this InputText to the current ConcreteAction
67+
if(action.get(Tags.InputText, null) != null)
68+
setSpecificAttribute(concreteAction, Tags.InputText, action.get(Tags.InputText));
69+
70+
// get a screenshot for this concrete action
71+
// in a headless environment, there may not be screenshots
72+
String srcPath = action.get(Tags.ActionScreenshotPath, null);
73+
if (srcPath != null && !srcPath.isEmpty()) {
74+
Path normalizePath = Paths.get(srcPath).normalize();
75+
76+
// wait for action screenshot to be saved (max ~2s)
77+
for (int i = 0; i < 20 && !Files.isRegularFile(normalizePath); i++) {
78+
Util.pause(0.1);
79+
}
80+
81+
try {
82+
if (Files.isRegularFile(normalizePath)) {
83+
byte[] bytes = Files.readAllBytes(normalizePath);
84+
concreteAction.setScreenshot(bytes);
85+
} else {
86+
logger.log(Level.WARN,"Action screenshot file not found: {}", normalizePath.toAbsolutePath());
87+
}
88+
} catch (IOException e) {
89+
e.printStackTrace();
90+
}
91+
}
92+
5493
return concreteAction;
5594
}
5695

statemodel/src/org/testar/statemodel/analysis/AnalysisManager.java

Lines changed: 28 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -452,7 +452,7 @@ private List<Element> fetchConcreteLayer(String modelIdentifier, ODatabaseSessio
452452
// concrete actions
453453
stmt = "SELECT FROM (TRAVERSE in('isAbstractedBy').outE('ConcreteAction') FROM (SELECT FROM AbstractState WHERE modelIdentifier = :identifier)) WHERE @class = 'ConcreteAction'";
454454
resultSet = db.query(stmt, params);
455-
elements.addAll(fetchEdges(resultSet, "ConcreteAction"));
455+
elements.addAll(fetchConcreteActionEdges(resultSet, modelIdentifier));
456456
resultSet.close();
457457

458458
return elements;
@@ -655,6 +655,33 @@ private ArrayList<Element> fetchEdges(OResultSet resultSet, String className) {
655655
return elements;
656656
}
657657

658+
private ArrayList<Element> fetchConcreteActionEdges(OResultSet resultSet, String modelIdentifier) {
659+
ArrayList<Element> elements = new ArrayList<>();
660+
while (resultSet.hasNext()) {
661+
OResult result = resultSet.next();
662+
if (result.isEdge()) {
663+
Optional<OEdge> op = result.getEdge();
664+
if (!op.isPresent()) continue;
665+
OEdge actionEdge = op.get();
666+
OVertexDocument source = actionEdge.getProperty("out");
667+
OVertexDocument target = actionEdge.getProperty("in");
668+
Edge jsonEdge = new Edge("e" + formatId(actionEdge.getIdentity().toString()), "n" + formatId(source.getIdentity().toString()), "n" + formatId(target.getIdentity().toString()));
669+
for (String propertyName : actionEdge.getPropertyNames()) {
670+
if (propertyName.contains("in") || propertyName.contains("out")) {
671+
continue;
672+
}
673+
if (propertyName.equals("screenshot")) {
674+
processScreenShot(actionEdge.getProperty("screenshot"), "e" + formatId(actionEdge.getIdentity().toString()), modelIdentifier);
675+
continue;
676+
}
677+
jsonEdge.addProperty(getExportPropertyName(propertyName), actionEdge.getProperty(propertyName).toString());
678+
}
679+
elements.add(new Element(Element.GROUP_EDGES, jsonEdge, "ConcreteAction"));
680+
}
681+
}
682+
return elements;
683+
}
684+
658685
/**
659686
* This method saves screenshots to disk.
660687
* @param recordBytes

statemodel/src/org/testar/statemodel/persistence/orientdb/OrientDBManager.java

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
/***************************************************************************************************
22
*
3-
* Copyright (c) 2018 - 2025 Open Universiteit - www.ou.nl
4-
* Copyright (c) 2018 - 2025 Universitat Politecnica de Valencia - www.upv.es
3+
* Copyright (c) 2018 - 2026 Open Universiteit - www.ou.nl
4+
* Copyright (c) 2018 - 2026 Universitat Politecnica de Valencia - www.upv.es
55
*
66
* Redistribution and use in source and binary forms, with or without
77
* modification, are permitted provided that the following conditions are met:
@@ -567,6 +567,7 @@ public void persistSequenceNode(SequenceNode sequenceNode) {
567567
// get the concrete state and make a vertex out of it
568568
EntityClass concreteStateClass = EntityClassFactory.createEntityClass(EntityClassFactory.EntityClassName.ConcreteState);
569569
VertexEntity stateEntity = new VertexEntity(concreteStateClass);
570+
stateEntity.enableUpdate(false);
570571

571572
// hydrate the entity to a format the orient database can store
572573
try {

statemodel/src/org/testar/statemodel/persistence/orientdb/entity/EntityClassFactory.java

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
/***************************************************************************************************
22
*
3-
* Copyright (c) 2018 - 2025 Open Universiteit - www.ou.nl
4-
* Copyright (c) 2018 - 2025 Universitat Politecnica de Valencia - www.upv.es
3+
* Copyright (c) 2018 - 2026 Open Universiteit - www.ou.nl
4+
* Copyright (c) 2018 - 2026 Universitat Politecnica de Valencia - www.upv.es
55
*
66
* Redistribution and use in source and binary forms, with or without
77
* modification, are permitted provided that the following conditions are met:
@@ -281,6 +281,11 @@ private static EntityClass createConcreteActionClass() {
281281
actionId.setNullable(false);
282282
actionId.setIdentifier(false);
283283
concreteActionClass.addProperty(actionId);
284+
Property screenshot = new Property("screenshot", OType.BINARY);
285+
screenshot.setMandatory(false);
286+
screenshot.setNullable(true);
287+
screenshot.setIdentifier(false);
288+
concreteActionClass.addProperty(screenshot);
284289
Property counter = new Property("counter", OType.INTEGER);
285290
counter.setMandatory(true);
286291
counter.setNullable(false);

0 commit comments

Comments
 (0)