1111import java .util .List ;
1212import java .util .Locale ;
1313import java .util .Optional ;
14+ import java .util .function .Predicate ;
1415
1516import org .testar .core .action .Action ;
1617import org .testar .core .action .ActionRoles ;
1718import org .testar .core .action .AnnotatingActionCompiler ;
1819import org .testar .core .action .resolver .ActionResolver ;
1920import org .testar .core .action .resolver .ResolvedAction ;
2021import org .testar .core .alayer .Role ;
22+ import org .testar .core .state .State ;
2123import org .testar .core .state .Widget ;
2224import org .testar .core .tag .Tags ;
2325
@@ -30,46 +32,46 @@ public final class DescriptionActionResolver implements ActionResolver {
3032 @ Override
3133 public ResolvedAction resolve (Iterable <Action > actions , List <String > arguments ) {
3234 List <Action > orderedActions = orderedActions (actions );
35+ String mode = normalizedMode (arguments );
36+ String semanticText = requiredSemanticText (arguments , mode );
37+
38+ switch (mode ) {
39+ case "click" :
40+ return new ResolvedAction (resolveClickAction (orderedActions , semanticText ));
41+ case "type" :
42+ return new ResolvedAction (resolveTypeAction (orderedActions , semanticText , requiredInput (arguments , mode )));
43+ case "select" :
44+ return new ResolvedAction (resolveSelectAction (orderedActions , semanticText , requiredInput (arguments , mode )));
45+ default :
46+ throw new IllegalArgumentException ("Unsupported executeAction mode: " + mode );
47+ }
48+ }
49+
50+ private String normalizedMode (List <String > arguments ) {
3351 String selector = argumentAt (arguments , 0 );
3452 if (selector == null ) {
3553 throw new IllegalArgumentException ("executeAction requires an action selector" );
3654 }
55+ return selector .toLowerCase (Locale .ROOT );
56+ }
3757
38- String mode = selector . toLowerCase ( Locale . ROOT );
58+ private String requiredSemanticText ( List < String > arguments , String mode ) {
3959 String semanticText = argumentAt (arguments , 1 );
4060 if (semanticText == null || semanticText .isBlank ()) {
4161 throw new IllegalArgumentException ("executeAction " + mode + " requires semantic text" );
4262 }
63+ return semanticText ;
64+ }
4365
44- switch (mode ) {
45- case "click" :
46- return findSemanticAction (orderedActions , semanticText , false , false )
47- .orElseThrow (() -> new IllegalArgumentException (
48- "no click action matched semantic text: " + semanticText
49- ));
50- case "type" :
51- String inputText = joinArguments (arguments , 2 );
52- if (inputText == null || inputText .isBlank ()) {
53- throw new IllegalArgumentException ("executeAction type requires input text" );
54- }
55- ResolvedAction matchedAction = findSemanticAction (orderedActions , semanticText , true , false )
56- .orElseThrow (() -> new IllegalArgumentException (
57- "no type action matched semantic text: " + semanticText
58- ));
59- return new ResolvedAction (buildTypedAction (matchedAction .action (), inputText ));
60- case "select" :
61- String selectedValue = joinArguments (arguments , 2 );
62- if (selectedValue == null || selectedValue .isBlank ()) {
63- throw new IllegalArgumentException ("executeAction select requires a value" );
64- }
65- ResolvedAction matchedSelectAction = findSemanticAction (orderedActions , semanticText , false , true )
66- .orElseThrow (() -> new IllegalArgumentException (
67- "no select action matched semantic text: " + semanticText
68- ));
69- return new ResolvedAction (buildSelectAction (matchedSelectAction .action (), selectedValue ));
70- default :
71- throw new IllegalArgumentException ("Unsupported executeAction mode: " + selector );
66+ private String requiredInput (List <String > arguments , String mode ) {
67+ String input = joinArguments (arguments , 2 );
68+ if (input == null || input .isBlank ()) {
69+ if ("type" .equals (mode )) {
70+ throw new IllegalArgumentException ("executeAction type requires input text" );
71+ }
72+ throw new IllegalArgumentException ("executeAction " + mode + " requires a value" );
7273 }
74+ return input ;
7375 }
7476
7577 private List <Action > orderedActions (Iterable <Action > actions ) {
@@ -81,27 +83,69 @@ private List<Action> orderedActions(Iterable<Action> actions) {
8183 return orderedActions ;
8284 }
8385
84- private Optional <ResolvedAction > findSemanticAction (List <Action > orderedActions ,
85- String semanticText ,
86- boolean typeAction ,
87- boolean selectAction ) {
86+ private Action resolveClickAction (List <Action > orderedActions , String semanticText ) {
87+ return findRequiredAction (
88+ orderedActions ,
89+ semanticText ,
90+ this ::isClickAction ,
91+ "no click action matched semantic text: " + semanticText
92+ );
93+ }
94+
95+ private Action resolveTypeAction (List <Action > orderedActions , String semanticText , String inputText ) {
96+ Action templateAction = findRequiredAction (
97+ orderedActions ,
98+ semanticText ,
99+ this ::isTypeAction ,
100+ "no type action matched semantic text: " + semanticText
101+ );
102+ return buildTypedAction (templateAction , inputText );
103+ }
104+
105+ private Action resolveSelectAction (List <Action > orderedActions , String semanticText , String selectedValue ) {
106+ Action templateAction = findRequiredAction (
107+ orderedActions ,
108+ semanticText ,
109+ this ::isSelectAction ,
110+ "no select action matched semantic text: " + semanticText
111+ );
112+ return buildSelectAction (templateAction , selectedValue );
113+ }
114+
115+ private Action findRequiredAction (List <Action > orderedActions ,
116+ String semanticText ,
117+ Predicate <Action > actionFilter ,
118+ String errorMessage ) {
119+ return findSemanticAction (orderedActions , semanticText , actionFilter )
120+ .orElseThrow (() -> new IllegalArgumentException (errorMessage ));
121+ }
122+
123+ private Optional <Action > findSemanticAction (List <Action > orderedActions ,
124+ String semanticText ,
125+ Predicate <Action > actionFilter ) {
88126 String normalizedSemanticText = normalizeText (semanticText );
89- // Equals semantic
90- for (int index = 0 ; index < orderedActions .size (); index ++) {
91- Action action = orderedActions .get (index );
92- if (matchesAction (action , typeAction , selectAction )) {
93- if (normalizeText (action .get (Tags .Desc , action .toString ())).equals (normalizedSemanticText )) {
94- return Optional .of (new ResolvedAction (action ));
95- }
96- }
127+ Optional <Action > exactMatch = findMatchingAction (
128+ orderedActions ,
129+ actionFilter ,
130+ action -> normalizedDescription (action ).equals (normalizedSemanticText )
131+ );
132+ if (exactMatch .isPresent ()) {
133+ return exactMatch ;
97134 }
98- // Contains semantic
99- for (int index = 0 ; index < orderedActions .size (); index ++) {
100- Action action = orderedActions .get (index );
101- if (matchesAction (action , typeAction , selectAction )) {
102- if (normalizeText (action .get (Tags .Desc , action .toString ())).contains (normalizedSemanticText )) {
103- return Optional .of (new ResolvedAction (action ));
104- }
135+
136+ return findMatchingAction (
137+ orderedActions ,
138+ actionFilter ,
139+ action -> normalizedDescription (action ).contains (normalizedSemanticText )
140+ );
141+ }
142+
143+ private Optional <Action > findMatchingAction (List <Action > orderedActions ,
144+ Predicate <Action > actionFilter ,
145+ Predicate <Action > semanticMatcher ) {
146+ for (Action action : orderedActions ) {
147+ if (actionFilter .test (action ) && semanticMatcher .test (action )) {
148+ return Optional .of (action );
105149 }
106150 }
107151 return Optional .empty ();
@@ -114,12 +158,20 @@ private Action buildTypedAction(Action templateAction, String inputText) {
114158 return templateAction ;
115159 }
116160
161+ Action rebuiltPlatformAction = rebuildTypedActionWithSameClass (templateAction , originWidget , inputText );
162+ if (rebuiltPlatformAction != null ) {
163+ return withCopiedIdentity (templateAction , rebuiltPlatformAction );
164+ }
165+
117166 Role role = templateAction .get (Tags .Role , null );
118167 if (role == ActionRoles .ClickTypeInto ) {
119168 if (originWidget == null ) {
120169 throw new IllegalArgumentException ("Matched type action does not have an origin widget" );
121170 }
122- return new AnnotatingActionCompiler ().clickTypeInto (originWidget , inputText , true );
171+ return withCopiedIdentity (
172+ templateAction ,
173+ new AnnotatingActionCompiler ().clickTypeInto (originWidget , inputText , true )
174+ );
123175 }
124176
125177 throw new IllegalArgumentException (
@@ -128,32 +180,64 @@ private Action buildTypedAction(Action templateAction, String inputText) {
128180 );
129181 }
130182
183+ private Action rebuildTypedActionWithSameClass (Action templateAction , Widget originWidget , String inputText ) {
184+ if (originWidget == null ) {
185+ return null ;
186+ }
187+
188+ Widget rootWidget = originWidget .root ();
189+ if (!(rootWidget instanceof State )) {
190+ return null ;
191+ }
192+
193+ try {
194+ return (Action ) templateAction .getClass ()
195+ .getConstructor (State .class , Widget .class , String .class )
196+ .newInstance ((State ) rootWidget , originWidget , inputText );
197+ } catch (ReflectiveOperationException exception ) {
198+ return null ;
199+ }
200+ }
201+
131202 private Action buildSelectAction (Action templateAction , String selectedValue ) {
132203 Widget originWidget = templateAction .get (Tags .OriginWidget , null );
133204 if (originWidget == null ) {
134205 throw new IllegalArgumentException ("Matched select action does not have an origin widget" );
135206 }
136207
208+ Action rebuiltAction = invokeStaticActionFactory (
209+ "org.testar.webdriver.action.WebdriverSelectListSupport" ,
210+ "createActionForInput" ,
211+ new Class <?>[] { Widget .class , String .class },
212+ new Object [] { originWidget , selectedValue },
213+ "Matched select action cannot be rebuilt without WebDriver select support"
214+ );
215+ if (rebuiltAction != null ) {
216+ return withCopiedIdentity (templateAction , rebuiltAction );
217+ }
218+
219+ throw new IllegalArgumentException ("Matched select action cannot be rebuilt due to missing target" );
220+ }
221+
222+ private Action invokeStaticActionFactory (String className ,
223+ String methodName ,
224+ Class <?>[] parameterTypes ,
225+ Object [] arguments ,
226+ String errorMessage ) {
137227 try {
138- Class <?> supportClass = Class .forName ("org.testar.webdriver.action.WebdriverSelectListSupport" );
139- Object selectAction = supportClass
140- .getMethod ("createActionForInput" , Widget .class , String .class )
141- .invoke (null , originWidget , selectedValue );
142- if (selectAction instanceof Action ) {
143- Action rebuiltAction = (Action ) selectAction ;
144- copyIdentityTags (templateAction , rebuiltAction );
145- return rebuiltAction ;
228+ Object action = Class .forName (className )
229+ .getMethod (methodName , parameterTypes )
230+ .invoke (null , arguments );
231+ if (action instanceof Action ) {
232+ return (Action ) action ;
146233 }
147234 } catch (ReflectiveOperationException exception ) {
148- throw new IllegalArgumentException (
149- "Matched select action cannot be rebuilt without WebDriver select support" ,
150- exception
151- );
235+ throw new IllegalArgumentException (errorMessage , exception );
152236 }
153- throw new IllegalArgumentException ( "Matched select action cannot be rebuilt due to missing target" ) ;
237+ return null ;
154238 }
155239
156- private void copyIdentityTags (Action sourceAction , Action targetAction ) {
240+ private Action withCopiedIdentity (Action sourceAction , Action targetAction ) {
157241 String abstractId = sourceAction .get (Tags .AbstractID , "" );
158242 if (!abstractId .isBlank ()) {
159243 targetAction .set (Tags .AbstractID , abstractId );
@@ -163,6 +247,8 @@ private void copyIdentityTags(Action sourceAction, Action targetAction) {
163247 if (!concreteId .isBlank ()) {
164248 targetAction .set (Tags .ConcreteID , concreteId );
165249 }
250+
251+ return targetAction ;
166252 }
167253
168254 private boolean isWebdriverTypeAction (Action action ) {
@@ -182,32 +268,35 @@ private void applyWebdriverInputText(Action action, String inputText) {
182268 }
183269
184270 private boolean isClickAction (Action action ) {
185- Role role = action .get (Tags .Role , null );
186- String roleName = role == null ? "" : role .toString ().toLowerCase (Locale .ROOT );
271+ String roleName = roleNameOf (action );
187272 return roleName .contains ("click" ) && !roleName .contains ("type" );
188273 }
189274
190275 private boolean isTypeAction (Action action ) {
191- Role role = action .get (Tags .Role , null );
192- String roleName = role == null ? "" : role .toString ().toLowerCase (Locale .ROOT );
193- return roleName .contains ("type" );
276+ return roleNameOf (action ).contains ("type" );
194277 }
195278
196279 private boolean isSelectAction (Action action ) {
280+ return roleNameOf (action ).contains ("select" );
281+ }
282+
283+ private String roleNameOf (Action action ) {
197284 Role role = action .get (Tags .Role , null );
198- String roleName = role == null ? "" : role .toString ().toLowerCase (Locale .ROOT );
199- return roleName .contains ("select" );
285+ return role == null ? "" : role .toString ().toLowerCase (Locale .ROOT );
200286 }
201287
202- private boolean matchesAction (Action action , boolean typeAction , boolean selectAction ) {
203- if (selectAction ) {
204- return isSelectAction (action );
205- }
206- return typeAction ? isTypeAction (action ) : isClickAction (action );
288+ private String normalizedDescription (Action action ) {
289+ return normalizeText (action .get (Tags .Desc , action .toString ()));
207290 }
208291
209292 private String normalizeText (String text ) {
210- return text == null ? "" : text .toLowerCase (Locale .ROOT );
293+ if (text == null ) {
294+ return "" ;
295+ }
296+
297+ String normalized = text .toLowerCase (Locale .ROOT );
298+ normalized = normalized .replaceAll ("[\\ ?\\ *\\ u2022\\ u2023\\ u25e6\\ u2043\\ u2219\\ u25cf\\ ufffd]{2,}" , " passwordmask " );
299+ return normalized ;
211300 }
212301
213302 private String joinArguments (List <String > arguments , int startIndex ) {
0 commit comments