1 package io.guixer.tools;
2
3 import static com.google.common.base.Preconditions.checkNotNull;
4 import static io.guixer.tools.Locators.by;
5 import static java.nio.charset.StandardCharsets.UTF_8;
6 import static org.apache.commons.lang3.StringUtils.isBlank;
7 import static org.apache.commons.lang3.StringUtils.normalizeSpace;
8 import static org.apache.commons.lang3.StringUtils.substringBeforeLast;
9 import static org.junit.jupiter.api.Assertions.assertEquals;
10
11 import java.io.File;
12 import java.io.FileNotFoundException;
13 import java.io.IOException;
14 import java.io.InputStream;
15 import java.io.PrintWriter;
16 import java.io.StringWriter;
17 import java.lang.reflect.Constructor;
18 import java.lang.reflect.InvocationTargetException;
19 import java.lang.reflect.Method;
20 import java.time.Duration;
21 import java.util.Map;
22 import java.util.Properties;
23 import java.util.function.Consumer;
24
25 import javax.annotation.Nullable;
26
27 import org.apache.commons.io.FileUtils;
28 import org.apache.commons.lang3.NotImplementedException;
29 import org.apache.commons.lang3.tuple.Pair;
30 import org.joda.time.DateTime;
31 import org.junit.jupiter.api.Test;
32 import org.openqa.selenium.By;
33 import org.openqa.selenium.JavascriptExecutor;
34 import org.openqa.selenium.Keys;
35 import org.openqa.selenium.NoSuchElementException;
36 import org.openqa.selenium.OutputType;
37 import org.openqa.selenium.TakesScreenshot;
38 import org.openqa.selenium.WebDriver;
39 import org.openqa.selenium.WebElement;
40 import org.openqa.selenium.support.ui.ExpectedConditions;
41 import org.openqa.selenium.support.ui.WebDriverWait;
42
43 import io.guixer.ext.GuixerExtension;
44 import io.guixer.lang.AtomicStep;
45 import io.guixer.lang.GroupStep;
46 import io.guixer.lang.GuixerScenario;
47 import io.guixer.lang.SetLaneStep;
48 import io.guixer.lang.StatusStep;
49 import io.guixer.lang.Step;
50 import io.guixer.lang.command.AssertAbsentCommand;
51 import io.guixer.lang.command.AssertFalseCommand;
52 import io.guixer.lang.command.AssertPresentCommand;
53 import io.guixer.lang.command.AssertTrueCommand;
54 import io.guixer.lang.command.AttributeCommand;
55 import io.guixer.lang.command.CallCommand;
56 import io.guixer.lang.command.ClearCommand;
57 import io.guixer.lang.command.ClickCommand;
58 import io.guixer.lang.command.Command;
59 import io.guixer.lang.command.ExecuteScriptCommand;
60 import io.guixer.lang.command.ExtCommand;
61 import io.guixer.lang.command.FailureCommand;
62 import io.guixer.lang.command.GetCommand;
63 import io.guixer.lang.command.MessageCommand;
64 import io.guixer.lang.command.SendKeysCommand;
65 import io.guixer.lang.command.SetLaneCommand;
66 import io.guixer.lang.command.SetMaskedVariableCommand;
67 import io.guixer.lang.command.SetVariableCommand;
68 import io.guixer.lang.command.SleepCommand;
69 import io.guixer.lang.command.StatusCommand;
70 import io.guixer.lang.command.SuccessCommand;
71 import io.guixer.lang.command.SwitchToFrameCommand;
72 import io.guixer.lang.command.TagCommand;
73 import io.guixer.lang.command.WaitForCommand;
74 import io.guixer.lang.command.WaitForNotCommand;
75 import io.guixer.types.AttributeScope;
76
77 public class ScenarioRunner {
78
79 private final ExecutionContext context;
80 private final Callables callables;
81 private final GuixerScenario scenario;
82 private final File guixerOutDir;
83 private final File logFile;
84
85 private int screenshotCount = 0;
86 private int successCount = 0;
87 private int failureCount = 0;
88
89 public ScenarioRunner(
90 final ExecutionContext context,
91 final Callables callables,
92 final GuixerScenario scenario
93 ) throws IOException {
94
95 this.context = checkNotNull(context, "context");
96 this.callables = checkNotNull(callables, "callables");
97 this.scenario = checkNotNull(scenario, "scenario");
98
99 final DateTime now = new DateTime();
100
101 final long timeMillis = now.getMillis();
102
103 final Pair<Class<?>, String> testClassAndMethod = extractCurrentTestClassAndMethod();
104
105 final Class<?> testClass = testClassAndMethod.getLeft();
106 final String testMethodName = testClassAndMethod.getRight();
107
108 guixerOutDir = new File("target/guixer_out/"
109 + testClass.getSimpleName() + "/"
110 + testMethodName + "/"
111 + timeMillis);
112
113 FileUtils.forceMkdir(guixerOutDir);
114
115 logFile = new File(guixerOutDir, "test.log");
116
117 final String VERSION = getVersion();
118
119 FileUtils.write(logFile, "", UTF_8.name());
120
121 log("guixerVersion: " + VERSION);
122 log("date: " + now);
123 log("testClassName: " + testClass.getName());
124 log("testClassSimpleName: " + testClass.getSimpleName());
125 log("testMethodName: " + testMethodName);
126 log("timeMillis: " + timeMillis);
127 log("scenario: " + scenario.getName());
128
129 for (final Map.Entry<String, String> entry : scenario.getAttributes().entrySet()) {
130
131 final String attributeName = entry.getKey();
132
133 @Nullable
134 final String attributeValue = entry.getValue();
135
136 final AttributeScope attributeScope = AttributeScope.RUN;
137
138 if (attributeValue == null) {
139
140 log("tag: " + attributeScope
141 + " \"" + escapeQuotes(context.filterPlain(attributeName)) + "\"");
142
143 } else {
144
145 log("attribute: " + attributeScope
146 + " \"" + escapeQuotes(context.filterPlain(attributeName)) + "\""
147 + " \"" + escapeQuotes(context.filterPlain(attributeValue)) + "\"");
148 }
149 }
150 }
151
152 private static String getVersion() throws IOException {
153
154 final Properties properties = new Properties();
155
156 final String resourcePath = "application.properties";
157
158 final InputStream is = ScenarioRunner.class.getResourceAsStream(resourcePath);
159
160 if (is == null) {
161 throw new FileNotFoundException(resourcePath);
162 }
163
164 try {
165
166 properties.load(is);
167
168 } finally {
169 is.close();
170 }
171
172 final String propertyName = "project.version";
173
174 final String version = properties.getProperty(propertyName);
175
176 if (isBlank(version) || version.contains("$")) {
177 throw new IllegalStateException(
178 "Property " + propertyName + " in " + resourcePath + " has not been processed: " + version);
179 }
180
181 return version;
182 }
183
184 public void run(
185 final Consumer<RunnerContext> callable
186 ) throws IOException {
187
188 checkNotNull(callable, "callable");
189
190 final RunnerContext runnerContext = new RunnerContextImpl();
191
192 callable.accept(runnerContext);
193
194 IOException ioException = null;
195 RuntimeException runtimeException = null;
196 Error error = null;
197
198 try {
199
200 for (final Step step : scenario.getSteps()) {
201
202 runStep(step);
203 }
204
205 } catch (final IOException e) {
206
207 logWithMillis(e);
208
209 takeScreenshot();
210
211 ioException = e;
212
213 } catch (final RuntimeException e) {
214
215 logWithMillis(e);
216
217 takeScreenshot();
218
219 runtimeException = e;
220
221 } catch (final Error e) {
222
223 logWithMillis(e);
224
225 takeScreenshot();
226
227 error = e;
228 }
229
230 log();
231
232
233
234
235 log("successCount: " + successCount);
236 log("failureCount: " + failureCount);
237
238 log();
239
240 logWithMillis("Done.");
241
242 if (ioException != null) {
243
244 throw ioException;
245
246 } else if (runtimeException != null) {
247
248 throw runtimeException;
249
250 } else if (error != null) {
251
252 throw error;
253 }
254
255 assertEquals(0, failureCount, "failureCount");
256 }
257
258 @Nullable
259 private StepResult runStep(
260 final Step step
261 ) throws IOException {
262
263 checkNotNull(step, "step");
264
265 if (step instanceof GroupStep) {
266
267 return runGroupStep((GroupStep) step);
268
269 } else if (step instanceof AtomicStep) {
270
271 return runAtomicStep((AtomicStep) step);
272
273 } else if (step instanceof StatusStep) {
274
275 return runStatusStep((StatusStep) step);
276
277 } else if (step instanceof SetLaneStep) {
278
279 return runSetLaneStep((SetLaneStep) step);
280
281 } else if (step instanceof InlineStep) {
282
283 return runInlineStep((InlineStep) step);
284
285 } else {
286
287 throw new NotImplementedException("step.class: " + step.getClass().getName());
288 }
289 }
290
291 @Nullable
292 private StepResult runAtomicStep(
293 final AtomicStep step
294 ) throws IOException {
295
296 log();
297
298 logWithMillis("intent: " + normalizeSpace(step.getIntent()));
299
300 if (step.getSeq() != null) {
301
302 for (final Command command : step.getSeq()) {
303
304 runCommand(command);
305 }
306
307 } else {
308
309
310
311 runSetVariableCommand(step.getSetVariableCommand());
312 runSetMaskedVariableCommand(step.getSetMaskedVariableCommand());
313 runCallCommand(step.getCallCommand());
314 runGetCommand(step.getGetCommand());
315 runClearCommand(step.getClearCommand());
316 runSendKeysCommand(step.getSendKeysCommand());
317 runExecuteScriptCommand(step.getExecuteScriptCommand());
318 runClickCommand(step.getClickCommand());
319 runSwitchToFrameCommand(step.getSwitchToFrameCommand());
320 runSleepCommand(step.getSleepCommand());
321 runWaitForCommand(step.getWaitForCommand());
322 runWaitForNotCommand(step.getWaitForNotCommand());
323 runAssertPresentCommand(step.getAssertPresentCommand());
324 runAssertAbsentCommand(step.getAssertAbsentCommand());
325 runAssertTrueCommand(step.getAssertTrueCommand());
326 runAssertFalseCommand(step.getAssertFalseCommand());
327 runFailureCommand(step.getFailureCommand());
328 runSuccessCommand(step.getSuccessCommand());
329 runMessageCommand(step.getMessageCommand());
330 runTagCommand(step.getTagCommand());
331 runAttributeCommand(step.getAttributeCommand());
332 runUnprocessedExtCommand(step.getExtCommand());
333 }
334
335 if (step.hasTakeScreenshot()) {
336
337 takeScreenshot();
338 }
339
340 return null;
341 }
342
343 @Nullable
344 private StepResult runInlineStep(
345 final InlineStep step
346 ) throws IOException {
347
348 log();
349
350 logWithMillis("call: " + step.name);
351
352
353
354 callables.execute(step.consumer);
355
356 return null;
357 }
358
359 @Nullable
360 private CommandResult runCommand(
361 @Nullable final Command command
362 ) throws IOException {
363
364 if (command == null) {
365
366 return null;
367
368 } else if (command instanceof GetCommand) {
369
370 return runGetCommand((GetCommand) command);
371
372 } else if (command instanceof ClearCommand) {
373
374 return runClearCommand((ClearCommand) command);
375
376 } else if (command instanceof SendKeysCommand) {
377
378 return runSendKeysCommand((SendKeysCommand) command);
379
380 } else if (command instanceof ClickCommand) {
381
382 return runClickCommand((ClickCommand) command);
383
384 } else if (command instanceof CallCommand) {
385
386 return runCallCommand((CallCommand) command);
387
388 } else if (command instanceof ExecuteScriptCommand) {
389
390 return runExecuteScriptCommand((ExecuteScriptCommand) command);
391
392 } else if (command instanceof SwitchToFrameCommand) {
393
394 return runSwitchToFrameCommand((SwitchToFrameCommand) command);
395
396 } else if (command instanceof SleepCommand) {
397
398 return runSleepCommand((SleepCommand) command);
399
400 } else if (command instanceof WaitForCommand) {
401
402 return runWaitForCommand((WaitForCommand) command);
403
404 } else if (command instanceof WaitForNotCommand) {
405
406 return runWaitForNotCommand((WaitForNotCommand) command);
407
408 } else if (command instanceof FailureCommand) {
409
410 return runFailureCommand((FailureCommand) command);
411
412 } else if (command instanceof SuccessCommand) {
413
414 return runSuccessCommand((SuccessCommand) command);
415
416 } else if (command instanceof MessageCommand) {
417
418 return runMessageCommand((MessageCommand) command);
419
420 } else if (command instanceof AssertPresentCommand) {
421
422 return runAssertPresentCommand((AssertPresentCommand) command);
423
424 } else if (command instanceof AssertAbsentCommand) {
425
426 return runAssertAbsentCommand((AssertAbsentCommand) command);
427
428 } else if (command instanceof AssertTrueCommand) {
429
430 return runAssertTrueCommand((AssertTrueCommand) command);
431
432 } else if (command instanceof AssertFalseCommand) {
433
434 return runAssertFalseCommand((AssertFalseCommand) command);
435
436 } else if (command instanceof SetVariableCommand) {
437
438 return runSetVariableCommand((SetVariableCommand) command);
439
440 } else if (command instanceof SetMaskedVariableCommand) {
441
442 return runSetMaskedVariableCommand((SetMaskedVariableCommand) command);
443
444 } else if (command instanceof TagCommand) {
445
446 return runTagCommand((TagCommand) command);
447
448 } else if (command instanceof AttributeCommand) {
449
450 return runAttributeCommand((AttributeCommand) command);
451
452 } else if (command instanceof ExtCommand) {
453
454 return runUnprocessedExtCommand((ExtCommand) command);
455
456 } else {
457
458 throw new NotImplementedException("command.class: " + command.getClass().getName());
459 }
460 }
461
462 @Nullable
463 private CommandResult runGetCommand(
464 @Nullable final GetCommand command
465 ) throws IOException {
466
467 if (command == null) {
468 return null;
469 }
470
471 final String url = context.filterPlain(command.url);
472
473 logWithMillis("get: " + url);
474
475 context.getDriver().get(url);
476
477 return null;
478 }
479
480 private CommandResult runSendKeysCommand(
481 @Nullable final SendKeysCommand command
482 ) throws IOException {
483
484 if (command == null) {
485 return null;
486 }
487
488 final By by = by(command.locator);
489
490 final String filtered;
491
492 if (context.usesMaskedVariables(command.value)) {
493
494 filtered = context.filterMasked(command.value);
495
496 logWithMillis("sendKeys: " + by + " ***");
497
498 } else {
499
500 filtered = context.filterPlain(command.value);
501
502 logWithMillis("sendKeys: " + by + " '" + filtered + "'");
503 }
504
505 if ("\"\\n\"".equals(filtered) || "\\n".equals(filtered)) {
506
507 findElement(by).sendKeys(Keys.ENTER);
508
509 } else {
510
511 findElement(by).sendKeys(filtered);
512 }
513
514 return null;
515 }
516
517 private CommandResult runWaitForCommand(
518 @Nullable final WaitForCommand command
519 ) throws IOException {
520
521 if (command == null) {
522 return null;
523 }
524
525 final By by = by(command.locator);
526
527 logWithMillis("waitFor: " + by);
528
529 final WebDriverWait wait = new WebDriverWait(context.getDriver(), Duration.ofSeconds(20));
530
531 wait.until(ExpectedConditions.presenceOfElementLocated(by));
532
533 return null;
534 }
535
536 private CommandResult runWaitForNotCommand(
537 @Nullable final WaitForNotCommand command
538 ) throws IOException {
539
540 if (command == null) {
541 return null;
542 }
543
544 final By by = by(command.locator);
545
546 logWithMillis("waitForNot: " + by);
547
548 final WebDriverWait wait = new WebDriverWait(context.getDriver(), Duration.ofSeconds(20));
549
550 wait.until(ExpectedConditions.not(ExpectedConditions.presenceOfAllElementsLocatedBy(by)));
551
552 return null;
553 }
554
555 @Nullable
556 private CommandResult runClickCommand(
557 @Nullable final ClickCommand command
558 ) throws IOException {
559
560 if (command == null) {
561 return null;
562 }
563
564 final By by = by(command.locator);
565
566 logWithMillis("click: " + by);
567
568 findElement(by).click();
569
570 return null;
571 }
572
573 @Nullable
574 private CommandResult runClearCommand(
575 @Nullable final ClearCommand command
576 ) throws IOException {
577
578 if (command == null) {
579 return null;
580 }
581
582 final By by = by(command.locator);
583
584 logWithMillis("clear: " + by);
585
586 findElement(by).clear();
587
588 return null;
589 }
590
591 @Nullable
592 private CommandResult runSleepCommand(
593 @Nullable final SleepCommand command
594 ) throws IOException {
595
596 if (command == null) {
597 return null;
598 }
599
600 final int seconds = command.seconds;
601
602 logWithMillis("sleep: " + seconds);
603
604 try {
605
606 Thread.sleep(seconds * 1_000);
607
608 } catch (final InterruptedException e) {
609
610 e.printStackTrace();
611 }
612
613 return null;
614 }
615
616 @Nullable
617 private CommandResult runTagCommand(
618 @Nullable final TagCommand command
619 ) throws IOException {
620
621 if (command == null) {
622 return null;
623 }
624
625 logWithMillis("tag: " + command.scope
626 + " \"" + escapeQuotes(context.filterPlain(command.name)) + "\"");
627
628 return null;
629 }
630
631 @Nullable
632 private CommandResult runAttributeCommand(
633 @Nullable final AttributeCommand command
634 ) throws IOException {
635
636 if (command == null) {
637 return null;
638 }
639
640 logWithMillis("attribute: " + command.scope
641 + " \"" + escapeQuotes(context.filterPlain(command.name)) + "\""
642 + " \"" + escapeQuotes(context.filterPlain(command.value)) + "\"");
643
644 return null;
645 }
646
647 private static String escapeQuotes(
648 final String s
649 ) {
650
651 return s.replace("\\", "\\\\").replace("\"", "\\\"");
652 }
653
654 @Nullable
655 private CommandResult runProcessedExtCommand(
656 final ExtCommand command
657 ) throws IOException {
658
659 checkNotNull(command, "command");
660
661 logWithMillis("ext: \"" + escapeQuotes(context.filterPlain(command.namespace)) + "\""
662 + " \"" + escapeQuotes(context.filterPlain(command.text)) + "\"");
663
664 return null;
665 }
666
667 @Nullable
668 private CommandResult runUnprocessedExtCommand(
669 @Nullable final ExtCommand command
670 ) throws IOException {
671
672 if (command == null) {
673 return null;
674 }
675
676 final String className = command.namespace;
677
678 final Class<?> clazz;
679
680 try {
681
682 clazz = Class.forName(className);
683
684 } catch (final ClassNotFoundException e) {
685
686 throw new RuntimeException("Cannot load class: " + className, e);
687 }
688
689 if (!GuixerExtension.class.isAssignableFrom(clazz)) {
690
691 throw new RuntimeException("Extension class should implement GuixerExtension, but was: " + className);
692 }
693
694 final Constructor<?> constructor;
695
696 try {
697
698 constructor = clazz.getDeclaredConstructor();
699
700 } catch (final NoSuchMethodException e) {
701
702 throw new RuntimeException("Extension class should declare a default constructor, but was: " + className);
703 }
704
705 final GuixerExtension instance;
706
707 try {
708
709 instance = (GuixerExtension) constructor.newInstance();
710
711 } catch (final InvocationTargetException | InstantiationException | IllegalAccessException e) {
712
713 throw new RuntimeException("Cannot instantiate extension class: " + className, e);
714 }
715
716 final RunnerContext runnerContext = new RunnerContextImpl();
717
718 instance.executeCommand(runnerContext, command.text);
719
720 return null;
721 }
722
723 @Nullable
724 private CommandResult runCallCommand(
725 @Nullable final CallCommand command
726 ) throws IOException {
727
728 if (command == null) {
729 return null;
730 }
731
732 final String callable = command.callable;
733
734 logWithMillis("call: " + callable);
735
736 callables.call(callable);
737
738 return null;
739 }
740
741 @Nullable
742 private ExecuteScriptCommandResult runExecuteScriptCommand(
743 @Nullable final ExecuteScriptCommand command
744 ) throws IOException {
745
746 if (command == null) {
747 return null;
748 }
749
750 final String script = command.script;
751
752 logWithMillis("executeScript: " + script);
753
754 final JavascriptExecutor jsExecutor = (JavascriptExecutor) context.getDriver();
755
756 @Nullable
757 final Object result = jsExecutor.executeScript(script);
758
759 return new ExecuteScriptCommandResult() {
760
761 @Nullable
762 @Override
763 public Object get() {
764
765 return result;
766 }
767
768 @Override
769 public int asInt() {
770
771 if (result == null) {
772
773 throw new NullPointerException("The script returned no value.");
774
775 } else if (result instanceof Double) {
776
777 return ((Double) result).intValue();
778
779 } else if (result instanceof Long) {
780
781 return ((Long) result).intValue();
782
783 } else {
784
785 return (int) result;
786 }
787 }
788
789 @Override
790 public String asString() {
791
792 if (result == null) {
793
794 throw new NullPointerException("The script returned no value.");
795
796 } else if (result instanceof String) {
797
798 return (String) result;
799
800 } else {
801
802 return result.toString();
803 }
804 }
805 };
806 }
807
808 private WebElement findElement(
809 final By by
810 ) throws IOException {
811
812 return context.getDriver().findElement(by);
813 }
814
815 @Nullable
816 private CommandResult runSwitchToFrameCommand(
817 @Nullable final SwitchToFrameCommand command
818 ) throws IOException {
819
820 if (command == null) {
821 return null;
822 }
823
824 context.getDriver().switchTo().frame(command.frameName);
825
826 return null;
827 }
828
829 @Nullable
830 private CommandResult runFailureCommand(
831 @Nullable final FailureCommand command
832 ) throws IOException {
833
834 if (command == null) {
835 return null;
836 }
837
838 final long timeMillis = System.currentTimeMillis();
839
840 logWithMillis(timeMillis, "failure: " + command.message + " -> " + successOrFailure(false));
841
842 return null;
843 }
844
845 @Nullable
846 private CommandResult runSuccessCommand(
847 @Nullable final SuccessCommand command
848 ) throws IOException {
849
850 if (command == null) {
851 return null;
852 }
853
854 final long timeMillis = System.currentTimeMillis();
855
856 logWithMillis(timeMillis, "success: " + command.message + " -> " + successOrFailure(true));
857
858 return null;
859 }
860
861 @Nullable
862 private CommandResult runMessageCommand(
863 @Nullable final MessageCommand command
864 ) throws IOException {
865
866 if (command == null) {
867 return null;
868 }
869
870 final long timeMillis = System.currentTimeMillis();
871
872 logWithMillis(timeMillis, "message: " + command.text);
873
874 return null;
875 }
876
877 @Nullable
878 private CommandResult runAssertPresentCommand(
879 @Nullable final AssertPresentCommand command
880 ) throws IOException {
881
882 if (command == null) {
883 return null;
884 }
885
886 final By by = by(command.locator);
887
888 final long timeMillis = System.currentTimeMillis();
889
890 boolean isPresent = true;
891
892 try {
893
894 findElement(by);
895
896 } catch (final NoSuchElementException e) {
897
898 isPresent = false;
899 }
900
901 logWithMillis(timeMillis, "assertPresent: " + by + " -> " + successOrFailure(isPresent));
902
903 return null;
904 }
905
906 @Nullable
907 private CommandResult runAssertAbsentCommand(
908 @Nullable final AssertAbsentCommand command
909 ) throws IOException {
910
911 if (command == null) {
912 return null;
913 }
914
915 final By by = by(command.locator);
916
917 final long timeMillis = System.currentTimeMillis();
918
919 boolean isPresent = true;
920
921 try {
922
923 findElement(by);
924
925 } catch (final NoSuchElementException e) {
926
927 isPresent = false;
928 }
929
930 logWithMillis(timeMillis, "assertAbsent: " + by + " -> " + successOrFailure(!isPresent));
931
932 return null;
933 }
934
935 @Nullable
936 private CommandResult runAssertTrueCommand(
937 @Nullable final AssertTrueCommand command
938 ) throws IOException {
939
940 if (command == null) {
941 return null;
942 }
943
944 final String expression = command.expression;
945
946 if (!expression.endsWith("/@checked")) {
947
948 throw new NotImplementedException("expression: " + expression);
949 }
950
951 final String locator = substringBeforeLast(expression, "/@checked");
952
953 final By by = by(locator);
954
955 final boolean selected = findElement(by).isSelected();
956
957 logWithMillis("assertTrue: " + by + " @checked -> " + successOrFailure(selected));
958
959 return null;
960 }
961
962 @Nullable
963 private CommandResult runAssertFalseCommand(
964 @Nullable final AssertFalseCommand command
965 ) throws IOException {
966
967 if (command == null) {
968 return null;
969 }
970
971 final String expression = command.expression;
972
973 if (!expression.endsWith("/@checked")) {
974
975 throw new NotImplementedException("expression: " + expression);
976 }
977
978 final String locator = substringBeforeLast(expression, "/@checked");
979
980 final By by = by(locator);
981
982 final boolean selected = findElement(by).isSelected();
983
984 logWithMillis("assertFalse: " + by + " @checked -> " + successOrFailure(!selected));
985
986 return null;
987 }
988
989 private String successOrFailure(
990 final boolean success
991 ) {
992
993 if (success) {
994
995 ++successCount;
996
997 } else {
998
999 ++failureCount;
1000 }
1001
1002 return success ? "SUCCESS" : "FAILURE";
1003 }
1004
1005 private void takeScreenshot() throws IOException {
1006
1007 final long timeMillis = System.currentTimeMillis();
1008
1009 final String screenshotFileName = String.format("%08d", screenshotCount) + ".png";
1010
1011 ++screenshotCount;
1012
1013 final File file0 = ((TakesScreenshot) context.getDriver()).getScreenshotAs(OutputType.FILE);
1014
1015 final File file1 = new File(logFile.getParentFile(), screenshotFileName);
1016
1017 FileUtils.copyFile(file0, file1);
1018
1019 logWithMillis(timeMillis, "takeScreenshot: " + screenshotFileName);
1020 }
1021
1022 @Nullable
1023 private StepResult runGroupStep(
1024 final GroupStep group
1025 ) throws IOException {
1026
1027 log();
1028
1029 logWithMillis("beginGroup: " + group.getName());
1030
1031 for (final Step step : group.getSteps()) {
1032
1033 runStep(step);
1034 }
1035
1036 log();
1037
1038 logWithMillis("endGroup: " + group.getName());
1039
1040 return null;
1041 }
1042
1043 @Nullable
1044 private StepResult runStatusStep(
1045 final StatusStep status
1046 ) throws IOException {
1047
1048 runStatusCommand(new StatusCommand(status.getLabel()));
1049
1050 return null;
1051 }
1052
1053 @Nullable
1054 private CommandResult runStatusCommand(
1055 final StatusCommand status
1056 ) throws IOException {
1057
1058 log();
1059
1060 logWithMillis("status: " + status.label);
1061
1062 return null;
1063 }
1064
1065 @Nullable
1066 private StepResult runSetLaneStep(
1067 final SetLaneStep setLane
1068 ) throws IOException {
1069
1070 runSetLaneCommand(new SetLaneCommand(setLane.getLaneId()));
1071
1072 return null;
1073 }
1074
1075 private CommandResult runSetLaneCommand(
1076 final SetLaneCommand command
1077 ) throws IOException {
1078
1079 checkNotNull(command, "command");
1080
1081 final String laneId = command.laneId;
1082
1083 log();
1084
1085 logWithMillis("setLane: " + laneId);
1086
1087 context.switchToDriver(laneId);
1088
1089 return null;
1090 }
1091
1092 private CommandResult runSetVariableCommand(
1093 @Nullable final SetVariableCommand command
1094 ) throws IOException {
1095
1096 if (command == null) {
1097 return null;
1098 }
1099
1100 final String name = command.name;
1101 final String value = context.filterPlain(command.value);
1102
1103 logWithMillis("setVariable: " + name + " " + value);
1104
1105 context.setVariable(name, value);
1106
1107 return null;
1108 }
1109
1110 private CommandResult runSetMaskedVariableCommand(
1111 @Nullable final SetMaskedVariableCommand command
1112 ) throws IOException {
1113
1114 if (command == null) {
1115 return null;
1116 }
1117
1118 final String name = command.name;
1119 final String value = context.filterMasked(command.value);
1120
1121 logWithMillis("setMaskedVariable: " + name + " ***");
1122
1123 context.setMaskedVariable(name, value);
1124
1125 return null;
1126 }
1127
1128 private static Pair<Class<?>, String> extractCurrentTestClassAndMethod() {
1129
1130 Pair<Class<?>, String> testClassAndMethod = null;
1131
1132 for (final StackTraceElement ste : Thread.currentThread().getStackTrace()) {
1133
1134 final String className = ste.getClassName();
1135 final String methodName = ste.getMethodName();
1136
1137 final Class<?> clazz;
1138
1139 try {
1140
1141 clazz = Class.forName(className);
1142
1143 } catch (final ClassNotFoundException e) {
1144
1145 continue;
1146 }
1147
1148 final Method method;
1149
1150
1151
1152 try {
1153
1154 method = clazz.getMethod(methodName);
1155
1156 } catch (final NoSuchMethodException e) {
1157
1158 continue;
1159 }
1160
1161 if (method.isAnnotationPresent(Test.class)) {
1162
1163 testClassAndMethod = Pair.of(clazz, methodName);
1164 }
1165 }
1166
1167 if (testClassAndMethod == null) {
1168
1169 throw new IllegalStateException(
1170 "The run() method sould be called from a @Test-annotated method with no arguments.");
1171 }
1172
1173 return testClassAndMethod;
1174 }
1175
1176 private void log() throws IOException {
1177
1178 System.out.println();
1179
1180 FileUtils.write(logFile, "\n", UTF_8.name(), true);
1181 }
1182
1183 private void log(
1184 final String message
1185 ) throws IOException {
1186
1187 System.out.println(message);
1188
1189 FileUtils.write(logFile, message + "\n", UTF_8.name(), true);
1190 }
1191
1192 private void logWithMillis(
1193 final String message
1194 ) throws IOException {
1195
1196 logWithMillis(System.currentTimeMillis(), message);
1197 }
1198
1199 private void logWithMillis(
1200 final long timeMillis,
1201 final String message
1202 ) throws IOException {
1203
1204 System.out.println(message);
1205
1206 FileUtils.write(logFile, timeMillis + " " + message + "\n", UTF_8.name(), true);
1207 }
1208
1209 private void logWithMillis(
1210 final Throwable throwable
1211 ) throws IOException {
1212
1213 final long timeMillis = System.currentTimeMillis();
1214
1215 System.out.print("error: ");
1216
1217 throwable.printStackTrace(System.out);
1218
1219 final StringWriter sw = new StringWriter();
1220
1221 try (PrintWriter pw = new PrintWriter(sw)) {
1222
1223 throwable.printStackTrace(pw);
1224
1225 pw.flush();
1226 }
1227
1228 FileUtils.write(logFile, timeMillis + " error: " + sw.toString() + "\n", UTF_8.name(), true);
1229 }
1230
1231 private class RunnerContextImpl implements RunnerContext {
1232
1233 @Override
1234 public String[] getVariableNames() {
1235
1236 return context.getVariableNames();
1237 }
1238
1239 @Override
1240 public <T> T setVariable(
1241 final String name,
1242 final T value
1243 ) {
1244
1245 return context.setVariable(name, value);
1246 }
1247
1248 @Override
1249 public void setMaskedVariable(
1250 final String name,
1251 final Object value
1252 ) {
1253
1254 context.setMaskedVariable(name, value);
1255 }
1256
1257 @Override
1258 public Object getVariable(
1259 final String name
1260 ) {
1261
1262 return context.getVariable(name);
1263 }
1264
1265 @Override
1266 public void removeVariable(
1267 final String name
1268 ) {
1269
1270 context.removeVariable(name);
1271 }
1272
1273 @Override
1274 public boolean usesMaskedVariables(
1275 final String value
1276 ) {
1277
1278 return context.usesMaskedVariables(value);
1279 }
1280
1281 @Override
1282 public String filterPlain(
1283 final String value
1284 ) {
1285
1286 return context.filterPlain(value);
1287 }
1288
1289 @Override
1290 public String filterMasked(
1291 final String value
1292 ) {
1293
1294 return context.filterMasked(value);
1295 }
1296
1297 @Override
1298 public void switchToDriver(
1299 final String driverName
1300 ) {
1301
1302 context.switchToDriver(driverName);
1303 }
1304
1305 @Override
1306 public CommandResult intent(
1307 final String intent
1308 ) throws IOException {
1309
1310 checkNotNull(intent, "intent");
1311
1312 log();
1313
1314 logWithMillis("intent: " + normalizeSpace(intent));
1315
1316 return null;
1317 }
1318
1319 @Override
1320 public CommandResult get(
1321 final String url
1322 ) throws IOException {
1323
1324 return runGetCommand(new GetCommand(url));
1325 }
1326
1327 @Override
1328 public CommandResult clear(
1329 final String locator
1330 ) throws IOException {
1331
1332 return runClearCommand(new ClearCommand(locator));
1333 }
1334
1335 @Override
1336 public CommandResult sendKeys(
1337 final String locator,
1338 final String value
1339 ) throws IOException {
1340
1341 return runSendKeysCommand(new SendKeysCommand(locator, value));
1342 }
1343
1344 @Override
1345 public CommandResult click(
1346 final String locator
1347 ) throws IOException {
1348
1349 return runClickCommand(new ClickCommand(locator));
1350 }
1351
1352 @Override
1353 public CommandResult waitFor(
1354 final String locator
1355 ) throws IOException {
1356
1357 return runWaitForCommand(new WaitForCommand(locator));
1358 }
1359
1360 @Override
1361 public CommandResult waitForNot(
1362 final String locator
1363 ) throws IOException {
1364
1365 return runWaitForNotCommand(new WaitForNotCommand(locator));
1366 }
1367
1368 @Override
1369 public CommandResult failure(
1370 final String message
1371 ) throws IOException {
1372
1373 return runFailureCommand(new FailureCommand(message));
1374 }
1375
1376 @Override
1377 public CommandResult success(
1378 final String message
1379 ) throws IOException {
1380
1381 return runSuccessCommand(new SuccessCommand(message));
1382 }
1383
1384 @Override
1385 public CommandResult message(
1386 final String message
1387 ) throws IOException {
1388
1389 return runMessageCommand(new MessageCommand(message));
1390 }
1391
1392 @Override
1393 public CommandResult assertPresent(
1394 final String locator
1395 ) throws IOException {
1396
1397 return runAssertPresentCommand(new AssertPresentCommand(locator));
1398 }
1399
1400 @Override
1401 public CommandResult assertAbsent(
1402 final String locator
1403 ) throws IOException {
1404
1405 return runAssertAbsentCommand(new AssertAbsentCommand(locator));
1406 }
1407
1408 @Override
1409 public CommandResult assertTrue(
1410 final String expression
1411 ) throws IOException {
1412
1413 return runAssertTrueCommand(new AssertTrueCommand(expression));
1414 }
1415
1416 @Override
1417 public CommandResult assertFalse(
1418 final String expression
1419 ) throws IOException {
1420
1421 return runAssertFalseCommand(new AssertFalseCommand(expression));
1422 }
1423
1424 @Override
1425 public ExecuteScriptCommandResult executeScript(
1426 final String script
1427 ) throws IOException {
1428
1429 return runExecuteScriptCommand(new ExecuteScriptCommand(script));
1430 }
1431
1432 @Override
1433 public CommandResult setLane(
1434 final String laneId
1435 ) throws IOException {
1436
1437 return runSetLaneCommand(new SetLaneCommand(laneId));
1438 }
1439
1440 @Override
1441 public CommandResult sleep(
1442 final int seconds
1443 ) throws IOException {
1444
1445 return runSleepCommand(new SleepCommand(seconds));
1446 }
1447
1448 @Override
1449 public StepResult status(
1450 final String label
1451 ) throws IOException {
1452
1453 return runStatusStep(new StatusStep() {
1454
1455 @Override
1456 public String getLabel() {
1457
1458 return label;
1459 }
1460 });
1461 }
1462
1463 @Override
1464 public void takeScreenshot() throws IOException {
1465
1466 ScenarioRunner.this.takeScreenshot();
1467 }
1468
1469 @Override
1470 public WebDriver getDriver() {
1471
1472 return context.getDriver();
1473 }
1474
1475 @Override
1476 public CommandResult tag(
1477 final AttributeScope scope,
1478 final String name
1479 ) throws IOException {
1480
1481 return runTagCommand(new TagCommand(scope, name));
1482 }
1483
1484 @Override
1485 public CommandResult attribute(
1486 final AttributeScope scope,
1487 final String name,
1488 final String value
1489 ) throws IOException {
1490
1491 return runAttributeCommand(new AttributeCommand(scope, name, value));
1492 }
1493
1494 @Override
1495 public CommandResult ext(
1496 final String namespace,
1497 final String text
1498 ) throws IOException {
1499
1500 return runProcessedExtCommand(new ExtCommand(namespace, text));
1501 }
1502 }
1503 }