View Javadoc
1   package io.guixer.logs;
2   
3   import static com.google.common.base.Preconditions.checkNotNull;
4   import static net.avcompris.commons3.databeans.DataBeans.instantiate;
5   import static org.apache.commons.lang3.StringUtils.split;
6   
7   import java.io.File;
8   import java.io.IOException;
9   import java.util.Collection;
10  import java.util.List;
11  import java.util.Stack;
12  
13  import javax.annotation.Nullable;
14  
15  import org.apache.commons.io.FileUtils;
16  import org.apache.commons.lang3.NotImplementedException;
17  import org.joda.time.DateTime;
18  
19  import com.google.common.collect.Iterables;
20  import com.google.common.collect.Lists;
21  
22  import io.guixer.logs.ElaborateLog.Attribute;
23  import io.guixer.logs.ElaborateLog.Before;
24  import io.guixer.logs.ElaborateLog.Done;
25  import io.guixer.logs.ElaborateLog.Group;
26  import io.guixer.logs.ElaborateLog.Intent;
27  import io.guixer.logs.ElaborateLog.Locator;
28  import io.guixer.logs.ElaborateLog.LogError;
29  import io.guixer.logs.ElaborateLog.Screenshot;
30  import io.guixer.logs.ElaborateLog.Status;
31  import io.guixer.logs.ElaborateLog.Step;
32  import io.guixer.logs.LoggedBy.LoggedByCssSelector;
33  import io.guixer.logs.LoggedBy.LoggedById;
34  import io.guixer.logs.LoggedBy.LoggedByXPath;
35  import io.guixer.logs.lines.AssertAbsentLogLine;
36  import io.guixer.logs.lines.AssertPresentLogLine;
37  import io.guixer.logs.lines.AttributeLogLine;
38  import io.guixer.logs.lines.BeginGroupLogLine;
39  import io.guixer.logs.lines.CallLogLine;
40  import io.guixer.logs.lines.ClearLogLine;
41  import io.guixer.logs.lines.ClickLogLine;
42  import io.guixer.logs.lines.DoneLogLine;
43  import io.guixer.logs.lines.EndGroupLogLine;
44  import io.guixer.logs.lines.ErrorLogLine;
45  import io.guixer.logs.lines.ExecuteScriptLogLine;
46  import io.guixer.logs.lines.ExtLogLine;
47  import io.guixer.logs.lines.FailureLogLine;
48  import io.guixer.logs.lines.GetLogLine;
49  import io.guixer.logs.lines.IntentLogLine;
50  import io.guixer.logs.lines.LogLine;
51  import io.guixer.logs.lines.MessageLogLine;
52  import io.guixer.logs.lines.SendKeysLogLine;
53  import io.guixer.logs.lines.SetMaskedVariableLogLine;
54  import io.guixer.logs.lines.SetVariableLogLine;
55  import io.guixer.logs.lines.SleepLogLine;
56  import io.guixer.logs.lines.StatusLogLine;
57  import io.guixer.logs.lines.SuccessLogLine;
58  import io.guixer.logs.lines.TakeScreenshotLogLine;
59  import io.guixer.logs.lines.WaitForLogLine;
60  import io.guixer.logs.lines.WaitForNotLogLine;
61  import io.guixer.types.AttributeScope;
62  import io.guixer.types.LocatorType;
63  import io.guixer.types.ResultType;
64  import io.guixer.types.StepType;
65  import net.avcompris.binding.annotation.XPath;
66  import net.avcompris.binding.dom.helper.DomBinderUtils;
67  import net.avcompris.domdumper.Dumper;
68  import net.avcompris.domdumper.XMLDumpers;
69  
70  public abstract class ElaborateLogUtils {
71  
72  	public static ElaborateLog toElaborateLog(
73  		final Log log
74  	) {
75  		checkNotNull(log, "log");
76  
77  		final Attribute[] attributes = new Attribute[log.getAttributes().length];
78  
79  		for (int i = 0; i < attributes.length; ++i) {
80  
81  			final AttributeLogLine logLine = (AttributeLogLine) log.getAttributes()[i];
82  
83  			attributes[i] = new Attribute() {
84  
85  				@Override
86  				public AttributeScope getScope() {
87  					return logLine.getScope();
88  				}
89  
90  				@Override
91  				public String getName() {
92  					return logLine.getName();
93  				}
94  
95  				@Override
96  				@Nullable
97  				public String getValue() {
98  					return logLine.getValue();
99  				}
100 			};
101 		}
102 
103 		MutableIntent currentIntent = null;
104 		MutableBefore currentBefore = null;
105 		Done currentDone = null;
106 		MutableLogError currentError = null;
107 
108 		final Stack<MutableGroup> nestedGroups = new Stack<>();
109 
110 		final Intents rootIntents = new Intents() {
111 
112 			private final List<Intent> intents = Lists.newArrayList();
113 
114 			@Override
115 			public Intents addToIntents(
116 				final Intent intent
117 			) {
118 				intents.add(intent);
119 
120 				return this;
121 			}
122 
123 			@Override
124 			public Intent[] getIntents() {
125 
126 				return Iterables.toArray(intents, Intent.class);
127 			}
128 		};
129 
130 		// nestedIntents.push(intents);
131 
132 		Intents currentIntents = rootIntents;
133 
134 		for (final LogLine logLine : log.getLogLines()) {
135 
136 			final LogLine.Type type = logLine.getType();
137 			final long timeMillis = logLine.getTimeMillis();
138 
139 			if (type == LogLine.Type.INTENT) {
140 
141 				// e.g. 1713169947376 intent: We open the app
142 				//
143 				currentIntent = instantiate(MutableIntent.class) //
144 						.setTimeMillis(timeMillis) //
145 						.setTitle(((IntentLogLine) logLine).getIntent());
146 
147 				currentIntents.addToIntents(currentIntent);
148 
149 			} else if (type == LogLine.Type.BEGIN_GROUP) {
150 
151 				final String groupName = ((BeginGroupLogLine) logLine).getGroupName();
152 
153 				final MutableGroup group = instantiate(MutableGroup.class) //
154 						.setName(groupName) //
155 						.setBeginAtMs(timeMillis);
156 
157 				nestedGroups.push(group);
158 
159 				final MutableIntent intent = instantiate(MutableIntent.class) //
160 						.setTimeMillis(timeMillis) //
161 						.setTitle(groupName) //
162 						.setGroup(group);
163 
164 				currentIntents.addToIntents(intent);
165 
166 				currentIntent = null;
167 
168 				currentIntents = group;
169 
170 			} else if (type == LogLine.Type.END_GROUP) {
171 
172 				final String groupName = ((EndGroupLogLine) logLine).getGroupName();
173 
174 				final MutableGroup currentGroup = nestedGroups.peek();
175 
176 				if (!groupName.equals(currentGroup.getName())) {
177 					throw new IllegalStateException(
178 							"currentGroup.name: Expected: " + currentGroup.getName() + ", but was: " + groupName);
179 				}
180 
181 				currentGroup.setEndAtMs(timeMillis);
182 
183 				nestedGroups.pop();
184 
185 				currentIntents = nestedGroups.isEmpty() //
186 						? rootIntents //
187 						: nestedGroups.peek();
188 
189 			} else if (type == LogLine.Type.STATUS) {
190 
191 				final String label = ((StatusLogLine) logLine).getLabel();
192 
193 				final MutableIntent intent = instantiate(MutableIntent.class) //
194 						.setTimeMillis(timeMillis) //
195 						.setTitle(label) //
196 						.setStatus(instantiate(MutableStatus.class) //
197 								.setStatusAtMs(timeMillis) //
198 								.setLabel(label));
199 
200 				currentIntents.addToIntents(intent);
201 
202 			} else if (currentIntent == null) {
203 
204 				if (currentBefore == null) {
205 					currentBefore = instantiate(MutableBefore.class);
206 				}
207 
208 				if (type == LogLine.Type.CALL) {
209 
210 					currentBefore.addToSteps(instantiate(MutableStep.class) //
211 							.setType(StepType.CALL) //
212 							.setTimeMillis(timeMillis) //
213 							.setCallable(((CallLogLine) logLine).getCallable()));
214 
215 				} else if (type == LogLine.Type.ATTRIBUTE) {
216 
217 					final AttributeLogLine attributeLogLine = (AttributeLogLine) logLine;
218 
219 					currentBefore.addToSteps(instantiate(MutableStep.class) //
220 							.setType(StepType.ATTRIBUTE) //
221 							.setTimeMillis(timeMillis) //
222 							.setScope(attributeLogLine.getScope()) //
223 							.setName(attributeLogLine.getName()) //
224 							.setValue(attributeLogLine.getValue()));
225 
226 				} else {
227 
228 					throw new NotImplementedException("currentIntent == null, and type: " + type);
229 				}
230 
231 			} else if (type == LogLine.Type.ASSERT_ABSENT) {
232 
233 				final AssertAbsentLogLine assertAbsentLogLine = (AssertAbsentLogLine) logLine;
234 
235 				currentIntent.addToSteps(instantiate(MutableStep.class) //
236 						.setType(StepType.ASSERT_ABSENT) //
237 						.setTimeMillis(timeMillis) //
238 						.setLocator(toLocator(assertAbsentLogLine.getLocator())) //
239 						.setResultType(assertAbsentLogLine.isSuccess() //
240 								? ResultType.SUCCESS //
241 								: ResultType.FAILURE));
242 
243 			} else if (type == LogLine.Type.ASSERT_PRESENT) {
244 
245 				final AssertPresentLogLine assertPresentLogLine = (AssertPresentLogLine) logLine;
246 
247 				currentIntent.addToSteps(instantiate(MutableStep.class) //
248 						.setType(StepType.ASSERT_PRESENT) //
249 						.setTimeMillis(timeMillis) //
250 						.setLocator(toLocator(assertPresentLogLine.getLocator())) //
251 						.setResultType(assertPresentLogLine.isSuccess() //
252 								? ResultType.SUCCESS //
253 								: ResultType.FAILURE));
254 
255 			} else if (type == LogLine.Type.ATTRIBUTE) {
256 
257 				final AttributeLogLine attributeLogLine = (AttributeLogLine) logLine;
258 
259 				currentIntent.addToSteps(instantiate(MutableStep.class) //
260 						.setType(StepType.ATTRIBUTE) //
261 						.setTimeMillis(timeMillis) //
262 						.setScope(attributeLogLine.getScope()) //
263 						.setName(attributeLogLine.getName()) //
264 						.setValue(attributeLogLine.getValue()));
265 
266 			} else if (type == LogLine.Type.CALL) {
267 
268 				currentIntent.addToSteps(instantiate(MutableStep.class) //
269 						.setType(StepType.CALL) //
270 						.setTimeMillis(timeMillis) //
271 						.setCallable(((CallLogLine) logLine).getCallable()));
272 
273 			} else if (type == LogLine.Type.CLEAR) {
274 
275 				currentIntent.addToSteps(instantiate(MutableStep.class) //
276 						.setType(StepType.CLEAR) //
277 						.setTimeMillis(timeMillis) //
278 						.setLocator(toLocator(((ClearLogLine) logLine).getLocator())));
279 
280 			} else if (type == LogLine.Type.CLICK) {
281 
282 				currentIntent.addToSteps(instantiate(MutableStep.class) //
283 						.setType(StepType.CLICK) //
284 						.setTimeMillis(timeMillis) //
285 						.setLocator(toLocator(((ClickLogLine) logLine).getLocator())));
286 
287 			} else if (type == LogLine.Type.DONE) {
288 
289 				currentDone = instantiate(MutableDone.class) //
290 						.setTimeMillis(timeMillis) //
291 						.setMessage("Done.");
292 
293 			} else if (type == LogLine.Type.ERROR) {
294 
295 				currentError = instantiate(MutableLogError.class) //
296 						.setTimeMillis(timeMillis) //
297 						.setTrace(((ErrorLogLine) logLine).getTrace());
298 
299 			} else if (type == LogLine.Type.EXECUTE_SCRIPT) {
300 
301 				final ExecuteScriptLogLine executeScriptLogLine = (ExecuteScriptLogLine) logLine;
302 
303 				currentIntent.addToSteps(instantiate(MutableStep.class) //
304 						.setType(StepType.EXECUTE_SCRIPT) //
305 						.setTimeMillis(timeMillis) //
306 						.setScript(executeScriptLogLine.getScript()));
307 
308 			} else if (type == LogLine.Type.EXT) {
309 
310 				final ExtLogLine extLogLine = (ExtLogLine) logLine;
311 
312 				currentIntent.addToSteps(instantiate(MutableStep.class) //
313 						.setType(StepType.EXT) //
314 						.setTimeMillis(timeMillis) //
315 						.setNamespace(extLogLine.getNamespace()) //
316 						.setValue(extLogLine.getText()));
317 
318 			} else if (type == LogLine.Type.FAILURE) {
319 
320 				currentIntent.addToSteps(instantiate(MutableStep.class) //
321 						.setType(StepType.FAILURE) //
322 						.setTimeMillis(timeMillis) //
323 						.setResultType(ResultType.FAILURE) //
324 						.setMessage((((FailureLogLine) logLine).getMessage())));
325 
326 			} else if (type == LogLine.Type.GET) {
327 
328 				currentIntent.addToSteps(instantiate(MutableStep.class) //
329 						.setType(StepType.GET) //
330 						.setTimeMillis(timeMillis) //
331 						.setUrl(((GetLogLine) logLine).getUrl()));
332 
333 			} else if (type == LogLine.Type.MESSAGE) {
334 
335 				currentIntent.addToSteps(instantiate(MutableStep.class) //
336 						.setType(StepType.MESSAGE) //
337 						.setTimeMillis(timeMillis) //
338 						.setMessage(((MessageLogLine) logLine).getText()));
339 
340 			} else if (type == LogLine.Type.SEND_KEYS) {
341 
342 				final SendKeysLogLine sendKeysLogLine = (SendKeysLogLine) logLine;
343 
344 				currentIntent.addToSteps(instantiate(MutableStep.class) //
345 						.setType(StepType.SEND_KEYS) //
346 						.setTimeMillis(timeMillis) //
347 						.setLocator(toLocator(sendKeysLogLine.getLocator())) //
348 						.setValue(sendKeysLogLine.getValue()));
349 
350 			} else if (type == LogLine.Type.SET_MASKED_VARIABLE) {
351 
352 				final SetMaskedVariableLogLine setVariableLogLine = (SetMaskedVariableLogLine) logLine;
353 
354 				currentIntent.addToSteps(instantiate(MutableStep.class) //
355 						.setType(StepType.SET_MASKED_VARIABLE) //
356 						.setTimeMillis(timeMillis) //
357 						.setName(setVariableLogLine.getName()) //
358 						.setValue(setVariableLogLine.getValue()));
359 
360 			} else if (type == LogLine.Type.SET_VARIABLE) {
361 
362 				final SetVariableLogLine setVariableLogLine = (SetVariableLogLine) logLine;
363 
364 				currentIntent.addToSteps(instantiate(MutableStep.class) //
365 						.setType(StepType.SET_VARIABLE) //
366 						.setTimeMillis(timeMillis) //
367 						.setName(setVariableLogLine.getName()) //
368 						.setValue(setVariableLogLine.getValue()));
369 
370 			} else if (type == LogLine.Type.SLEEP) {
371 
372 				currentIntent.addToSteps(instantiate(MutableStep.class) //
373 						.setType(StepType.SLEEP) //
374 						.setTimeMillis(timeMillis) //
375 						.setSeconds((((SleepLogLine) logLine).getSeconds())));
376 
377 			} else if (type == LogLine.Type.SUCCESS) {
378 
379 				currentIntent.addToSteps(instantiate(MutableStep.class) //
380 						.setType(StepType.SUCCESS) //
381 						.setTimeMillis(timeMillis) //
382 						.setResultType(ResultType.SUCCESS) //
383 						.setMessage(((SuccessLogLine) logLine).getMessage()));
384 
385 			} else if (type == LogLine.Type.TAKE_SCREENSHOT) {
386 
387 				final String fileName = ((TakeScreenshotLogLine) logLine).getFileName();
388 
389 				if (currentError == null) {
390 
391 					currentIntent.addToSteps(instantiate(MutableStep.class) //
392 							.setType(StepType.TAKE_SCREENSHOT) //
393 							.setTimeMillis(timeMillis) //
394 							.setFileName(fileName));
395 
396 				} else {
397 
398 					currentError.setScreenshot(instantiate(MutableScreenshot.class) //
399 							.setTimeMillis(timeMillis) //
400 							.setFileName(fileName));
401 				}
402 
403 			} else if (type == LogLine.Type.WAIT_FOR) {
404 
405 				currentIntent.addToSteps(instantiate(MutableStep.class) //
406 						.setType(StepType.WAIT_FOR) //
407 						.setTimeMillis(timeMillis) //
408 						.setLocator(toLocator(((WaitForLogLine) logLine).getLocator())));
409 
410 			} else if (type == LogLine.Type.WAIT_FOR_NOT) {
411 
412 				currentIntent.addToSteps(instantiate(MutableStep.class) //
413 						.setType(StepType.WAIT_FOR_NOT) //
414 						.setTimeMillis(timeMillis) //
415 						.setLocator(toLocator(((WaitForNotLogLine) logLine).getLocator())));
416 
417 			} else {
418 
419 				throw new NotImplementedException("type: " + type);
420 			}
421 		}
422 
423 		@Nullable
424 		final Before before = currentBefore;
425 
426 		@Nullable
427 		final Done done = currentDone;
428 
429 		@Nullable
430 		final LogError error = currentError;
431 
432 		return new ElaborateLog() {
433 
434 			@Override
435 			public byte[] getRawBytes() {
436 				return log.getRawBytes();
437 			}
438 
439 			@Override
440 			public String getGuixerVersion() {
441 				return log.getGuixerVersion();
442 			}
443 
444 			@Override
445 			public String getDateAsString() {
446 				return log.getDateAsString();
447 			}
448 
449 			@Override
450 			public DateTime getDate() {
451 				return log.getDate();
452 			}
453 
454 			@Override
455 			public String getTestClassName() {
456 				return log.getTestClassName();
457 			}
458 
459 			@Override
460 			public String getTestClassSimpleName() {
461 				return log.getTestClassSimpleName();
462 			}
463 
464 			@Override
465 			public String getTestMethodName() {
466 				return log.getTestMethodName();
467 			}
468 
469 			@Override
470 			public long getTimeMillis() {
471 				return log.getTimeMillis();
472 			}
473 
474 			@Override
475 			public String getScenario() {
476 				return log.getScenario();
477 			}
478 
479 			@Override
480 			public boolean isInError() {
481 				return log.isInError();
482 			}
483 
484 			@Override
485 			@Nullable
486 			public LogError getError() {
487 				return error;
488 			}
489 
490 			@Override
491 			public int getSuccessCount() {
492 				return log.getSuccessCount();
493 			}
494 
495 			@Override
496 			public int getFailureCount() {
497 				return log.getFailureCount();
498 			}
499 
500 			@Override
501 			public Attribute[] getAttributes() {
502 				return attributes;
503 			}
504 
505 			@Override
506 			@Nullable
507 			public Before getBefore() {
508 				return before;
509 			}
510 
511 			@Override
512 			public Intent[] getIntents() {
513 				// return Iterables.toArray(rootIntents, Intent.class);
514 				return rootIntents.getIntents();
515 			}
516 
517 			@Override
518 			public Done getDone() {
519 				return done;
520 			}
521 		};
522 	}
523 
524 	private static Locator toLocator(
525 		final LoggedBy loggedBy
526 	) {
527 
528 		checkNotNull(loggedBy, "loggedBy");
529 
530 		if (loggedBy instanceof LoggedById) {
531 
532 			return instantiate(MutableLocator.class) //
533 					.setType(LocatorType.BY_ID) //
534 					.setValue(((LoggedById) loggedBy).id);
535 
536 		} else if (loggedBy instanceof LoggedByCssSelector) {
537 
538 			return instantiate(MutableLocator.class) //
539 					.setType(LocatorType.BY_CSS_SELECTOR) //
540 					.setValue(((LoggedByCssSelector) loggedBy).cssSelector);
541 
542 		} else if (loggedBy instanceof LoggedByXPath) {
543 
544 			return instantiate(MutableLocator.class) //
545 					.setType(LocatorType.BY_XPATH) //
546 					.setValue(((LoggedByXPath) loggedBy).xpathExpression);
547 
548 		} else {
549 
550 			throw new NotImplementedException("loggedBy: " + loggedBy);
551 		}
552 	}
553 
554 	public interface MutableIntent extends Intent {
555 
556 		MutableIntent setTimeMillis(
557 			long timeMillis
558 		);
559 
560 		MutableIntent setTitle(
561 			String title
562 		);
563 
564 		MutableIntent addToSteps(
565 			Step steps
566 		);
567 
568 		MutableIntent setGroup(
569 			@Nullable Group group
570 		);
571 
572 		MutableIntent setStatus(
573 			@Nullable Status status
574 		);
575 	}
576 
577 	public interface MutableStep extends Step {
578 
579 		MutableStep setTimeMillis(
580 			long timeMillis
581 		);
582 
583 		MutableStep setType(
584 			StepType type
585 		);
586 
587 		MutableStep setUrl(
588 			@Nullable String url
589 		);
590 
591 		MutableStep setFileName(
592 			@Nullable String fileName
593 		);
594 
595 		MutableStep setNamespace(
596 			@Nullable String namespace
597 		);
598 
599 		MutableStep setScope(
600 			@Nullable AttributeScope scope
601 		);
602 
603 		MutableStep setScript(
604 			@Nullable String script
605 		);
606 
607 		MutableStep setName(
608 			@Nullable String name
609 		);
610 
611 		MutableStep setValue(
612 			@Nullable String value
613 		);
614 
615 		MutableStep setLocator(
616 			@Nullable Locator locator
617 		);
618 
619 		MutableStep setMessage(
620 			@Nullable String message
621 		);
622 
623 		MutableStep setResultType(
624 			@Nullable ResultType resultType
625 		);
626 
627 		MutableStep setCallable(
628 			@Nullable String callable
629 		);
630 
631 		MutableStep setSeconds(
632 			@Nullable Integer seconds
633 		);
634 
635 		MutableStep setLabel(
636 			@Nullable String label
637 		);
638 	}
639 
640 	public interface MutableLocator extends Locator {
641 
642 		MutableLocator setType(
643 			LocatorType type
644 		);
645 
646 		MutableLocator setValue(
647 			String value
648 		);
649 	}
650 
651 	public interface MutableAttribute extends Attribute {
652 
653 		MutableAttribute setScope(
654 			AttributeScope scope
655 		);
656 
657 		MutableAttribute setName(
658 			String name
659 		);
660 
661 		MutableAttribute setValue(
662 			@Nullable String name
663 		);
664 	}
665 
666 	public interface MutableDone extends Done {
667 
668 		MutableDone setTimeMillis(
669 			long timeMillis
670 		);
671 
672 		MutableDone setMessage(
673 			String message
674 		);
675 	}
676 
677 	public interface MutableBefore extends Before {
678 
679 		MutableBefore addToSteps(
680 			Step step
681 		);
682 	}
683 
684 	public interface MutableGroup extends Group, Intents {
685 
686 		MutableGroup setName(
687 			String name
688 		);
689 
690 		MutableGroup setBeginAtMs(
691 			long beginAtMs
692 		);
693 
694 		MutableGroup setEndAtMs(
695 			@Nullable Long endAtMs
696 		);
697 
698 		MutableGroup setIntents(
699 			Intent[] intents
700 		);
701 
702 		@Override
703 		MutableGroup addToIntents(
704 			Intent intent
705 		);
706 	}
707 
708 	public interface MutableStatus extends Status {
709 
710 		MutableStatus setStatusAtMs(
711 			long timeMillis
712 		);
713 
714 		MutableStatus setLabel(
715 			String label
716 		);
717 	}
718 
719 	public interface Intents {
720 
721 		Intents addToIntents(
722 			Intent intent
723 		);
724 
725 		Intent[] getIntents();
726 	}
727 
728 	public interface MutableLogError extends LogError {
729 
730 		MutableLogError setTimeMillis(
731 			long timeMillis
732 		);
733 
734 		MutableLogError setTrace(
735 			String trace
736 		);
737 
738 		MutableLogError setScreenshot(
739 			Screenshot screenshot
740 		);
741 	}
742 
743 	public interface MutableScreenshot extends Screenshot {
744 
745 		MutableScreenshot setTimeMillis(
746 			long timeMillis
747 		);
748 
749 		MutableScreenshot setFileName(
750 			String fileName
751 		);
752 	}
753 
754 	public static void generateXML(
755 		final ElaborateLog log,
756 		final File xmlOutFile
757 	) throws IOException {
758 
759 		final Dumper dumper = XMLDumpers.newDumper("log", xmlOutFile, "UTF-8") //
760 				.addAttribute("guixerVersion", log.getGuixerVersion()) //
761 				.addAttribute("date", log.getDateAsString()) //
762 				.addAttribute("timeMillis", log.getTimeMillis()) //
763 				.addAttribute("testClassName", log.getTestClassName()) //
764 				.addAttribute("testClassSimpleName", log.getTestClassSimpleName()) //
765 				.addAttribute("testMethodName", log.getTestMethodName()) //
766 				.addAttribute("scenario", log.getScenario()) //
767 				.addAttribute("failureCount", log.getFailureCount()) //
768 				.addAttribute("successCount", log.getSuccessCount());
769 
770 		for (final Attribute attribute : log.getAttributes()) {
771 
772 			dumper.addElement("attribute") //
773 					.addAttribute("scope", attribute.getScope().name()) //
774 					.addAttribute("name", attribute.getName()) //
775 					.addAttribute("value", attribute.getValue());
776 		}
777 
778 		if (log.getBefore() != null) {
779 
780 			final Dumper beforeDumper = dumper.addElement("before");
781 
782 			dumpSteps(beforeDumper, log.getBefore().getSteps());
783 		}
784 
785 		dumpIntents(dumper, log.getIntents());
786 
787 		if (log.isInError()) {
788 
789 			final LogError error = log.getError();
790 
791 			dumper.addElement("error") //
792 					.addAttribute("timeMillis", error.getTimeMillis()) //
793 					.addElement("screenshot") //
794 					.addAttribute("timeMillis", error.getScreenshot().getTimeMillis()) //
795 					.addAttribute("fileName", error.getScreenshot().getFileName()) //
796 					.close() //
797 					.addElement("trace") //
798 					.addCharacters(error.getTrace());
799 		}
800 
801 		if (log.getDone() != null) {
802 
803 			final Done done = log.getDone();
804 
805 			dumper.addElement("done") //
806 					.addAttribute("timeMillis", done.getTimeMillis()) //
807 					.addAttribute("message", done.getMessage());
808 		}
809 
810 		dumper.close();
811 	}
812 
813 	private static void dumpIntents(
814 		final Dumper dumper,
815 		final Intent[] intents
816 	) throws IOException {
817 
818 		for (final Intent intent : intents) {
819 
820 			final Dumper intentDumper = dumper.addElement("intent") //
821 					.addAttribute("timeMillis", intent.getTimeMillis()) //
822 					.addAttribute("title", intent.getTitle());
823 
824 			if (intent.getGroup() != null) {
825 
826 				final Group group = intent.getGroup();
827 
828 				final Dumper groupDumper = intentDumper.addElement("group") //
829 						.addAttribute("name", group.getName()) //
830 						.addAttribute("beginAtMs", group.getBeginAtMs()) //
831 						.addAttribute("title", intent.getTitle());
832 
833 				if (group.getEndAtMs() != null) {
834 					groupDumper.addAttribute("endAtMs", group.getEndAtMs());
835 				}
836 
837 				dumpIntents(groupDumper, group.getIntents());
838 
839 			} else if (intent.getStatus() != null) {
840 
841 				final Status status = intent.getStatus();
842 
843 				intentDumper.addElement("status") //
844 						.addAttribute("statusAtMs", status.getStatusAtMs()) //
845 						.addAttribute("label", status.getLabel());
846 
847 			} else {
848 
849 				dumpSteps(intentDumper, intent.getSteps());
850 			}
851 		}
852 
853 	}
854 
855 	private static void dumpSteps(
856 		final Dumper dumper,
857 		final Step[] steps
858 	) throws IOException {
859 
860 		for (final Step step : steps) {
861 
862 			final Dumper stepDumper = dumper.addElement("step") //
863 					.addAttribute("type", step.getType().name()) //
864 					.addAttribute("callable", step.getCallable()) //
865 					.addAttribute("fileName", step.getFileName()) //
866 					.addAttribute("label", step.getLabel()) //
867 					.addAttribute("message", step.getMessage()) //
868 					.addAttribute("name", step.getName()) //
869 					.addAttribute("namespace", step.getNamespace()) //
870 					.addAttribute("script", step.getScript()) //
871 					.addAttribute("timeMillis", step.getTimeMillis()) //
872 					.addAttribute("url", step.getUrl()) //
873 					.addAttribute("value", step.getValue()) //
874 					.addAttribute("scope", step.getScope() == null ? null : step.getScope().name()) //
875 					.addAttribute("resultType", step.getResultType() == null ? null : step.getResultType().name());
876 
877 			if (step.getSeconds() != null) {
878 				stepDumper.addAttribute("seconds", step.getSeconds());
879 			}
880 
881 			if (step.getLocator() != null) {
882 
883 				final Locator locator = step.getLocator();
884 
885 				stepDumper.addElement("locator") //
886 						.addAttribute("type", locator.getType().name()) //
887 						.addAttribute("value", locator.getValue());
888 			}
889 		}
890 	}
891 
892 	public static ElaborateLog loadFromXML(
893 		final File xmlFile
894 	) throws IOException {
895 
896 		final ElaborateLogBinding binding = DomBinderUtils.xmlContentToJava(xmlFile, ElaborateLogBinding.class);
897 
898 		final byte[] rawBytes = FileUtils.readFileToByteArray(xmlFile);
899 
900 		return new ElaborateLog() {
901 
902 			@Override
903 			public byte[] getRawBytes() {
904 				return rawBytes;
905 			}
906 
907 			@Override
908 			public String getGuixerVersion() {
909 				return binding.getGuixerVersion();
910 			}
911 
912 			@Override
913 			public String getDateAsString() {
914 				return binding.getDateAsString();
915 			}
916 
917 			@Override
918 			@Nullable
919 			public DateTime getDate() {
920 				return null;
921 			}
922 
923 			@Override
924 			public String getTestClassName() {
925 				return binding.getTestClassName();
926 			}
927 
928 			@Override
929 			public String getTestClassSimpleName() {
930 				return binding.getTestClassSimpleName();
931 			}
932 
933 			@Override
934 			public String getTestMethodName() {
935 				return binding.getTestMethodName();
936 			}
937 
938 			@Override
939 			public long getTimeMillis() {
940 				return binding.getTimeMillis();
941 			}
942 
943 			@Override
944 			public String getScenario() {
945 				return binding.getScenario();
946 			}
947 
948 			@Override
949 			public boolean isInError() {
950 				return binding.getError() != null;
951 			}
952 
953 			@Override
954 			public int getSuccessCount() {
955 				return binding.isNullSuccessCount() ? 0 : binding.getSuccessCount();
956 			}
957 
958 			@Override
959 			public int getFailureCount() {
960 				return binding.isNullFailureCount() ? 0 : binding.getFailureCount();
961 			}
962 
963 			@Override
964 			public Attribute[] getAttributes() {
965 				return binding.getAttributes();
966 			}
967 
968 			@Override
969 			@Nullable
970 			public Before getBefore() {
971 				return binding.getBefore();
972 			}
973 
974 			@Override
975 			public Intent[] getIntents() {
976 				return binding.getIntents();
977 			}
978 
979 			@Override
980 			@Nullable
981 			public Done getDone() {
982 				return binding.getDone();
983 			}
984 
985 			@Override
986 			@Nullable
987 			public LogError getError() {
988 				return binding.getError();
989 			}
990 		};
991 	}
992 
993 	private interface ElaborateLogBinding {
994 
995 		@XPath("@guixerVersion")
996 		String getGuixerVersion();
997 
998 		@XPath("@date")
999 		String getDateAsString();
1000 
1001 		// @XPath("@date")
1002 		// @Nullable
1003 		// String getDate();
1004 
1005 		@XPath("@testClassName")
1006 		String getTestClassName();
1007 
1008 		@XPath("@testClassSimpleName")
1009 		String getTestClassSimpleName();
1010 
1011 		@XPath("@testMethodName")
1012 		String getTestMethodName();
1013 
1014 		@XPath("@timeMillis")
1015 		long getTimeMillis();
1016 
1017 		@Nullable
1018 		@XPath("@scenario")
1019 		String getScenario();
1020 
1021 		boolean isNullSuccessCount();
1022 
1023 		@Nullable
1024 		@XPath("@successCount")
1025 		Integer getSuccessCount();
1026 
1027 		boolean isNullFailureCount();
1028 
1029 		@Nullable
1030 		@XPath("@failureCount")
1031 		Integer getFailureCount();
1032 
1033 		@XPath("attribute")
1034 		Attribute[] getAttributes();
1035 
1036 		@Nullable
1037 		@XPath("before")
1038 		Before getBefore();
1039 
1040 		@XPath("intent")
1041 		Intent[] getIntents();
1042 
1043 		@Nullable
1044 		@XPath("done")
1045 		Done getDone();
1046 
1047 		boolean isNullError();
1048 
1049 		@Nullable
1050 		@XPath("error")
1051 		LogError getError();
1052 	}
1053 
1054 	private static void dumpAsGuixerOutLogLines(
1055 		final Intent intent,
1056 		final Collection<LogLine> logLines
1057 	) {
1058 
1059 		final Group group = intent.getGroup();
1060 		final Status status = intent.getStatus();
1061 
1062 		if (group != null) {
1063 
1064 			logLines.add(new BeginGroupLogLine( //
1065 					group.getBeginAtMs(), //
1066 					"beginGroup: " + group.getName(), // rawText
1067 					group.getName()));
1068 
1069 			for (final Intent subIntent : group.getIntents()) {
1070 
1071 				dumpAsGuixerOutLogLines(subIntent, logLines);
1072 			}
1073 
1074 			if (group.getEndAtMs() != null) {
1075 				logLines.add(new EndGroupLogLine( //
1076 						group.getEndAtMs(), //
1077 						"endGroup: " + group.getName(), //
1078 						group.getName()));
1079 			}
1080 
1081 		} else if (status != null) {
1082 
1083 			logLines.add(new StatusLogLine( //
1084 					status.getStatusAtMs(), //
1085 					"status: " + status.getLabel(), //
1086 					status.getLabel()));
1087 
1088 		} else {
1089 
1090 			logLines.add(new IntentLogLine( //
1091 					intent.getTimeMillis(), //
1092 					"intent: " + intent.getTitle(), //
1093 					intent.getTitle()));
1094 
1095 			for (final Step step : intent.getSteps()) {
1096 
1097 				logLines.add(toGuixerOutLogLine(step));
1098 			}
1099 		}
1100 	}
1101 
1102 	private static LoggedBy toLoggedBy(
1103 		final Locator locator
1104 	) {
1105 
1106 		switch (locator.getType()) {
1107 		case BY_ID:
1108 			return LoggedBy.id(locator.getValue());
1109 		case BY_CSS_SELECTOR:
1110 			return LoggedBy.cssSelector(locator.getValue());
1111 		case BY_XPATH:
1112 			return LoggedBy.xpath(locator.getValue());
1113 		default:
1114 			throw new NotImplementedException("locator.type: " + locator.getType());
1115 		}
1116 	}
1117 
1118 	private static String serialize(
1119 		final Locator locator
1120 	) {
1121 
1122 		switch (locator.getType()) {
1123 		case BY_ID:
1124 			return "By.id: " + locator.getValue();
1125 		case BY_CSS_SELECTOR:
1126 			return "By.cssSelector: " + locator.getValue();
1127 		case BY_XPATH:
1128 			return "By.xpath: " + locator.getValue();
1129 		default:
1130 			throw new NotImplementedException("locator.type: " + locator.getType());
1131 		}
1132 	}
1133 
1134 	private static String inQuotes(
1135 		final String s
1136 	) {
1137 
1138 		return "\"" + s.replace("\\", "\\\\").replace("\"", "\\\"") + "\"";
1139 	}
1140 
1141 	private static LogLine toGuixerOutLogLine(
1142 		final Step step
1143 	) {
1144 		switch (step.getType()) {
1145 		case ASSERT_ABSENT:
1146 			return new AssertAbsentLogLine( //
1147 					step.getTimeMillis(), //
1148 					"assertAbsent: " + serialize(step.getLocator()) //
1149 							+ " -> " + step.getResultType(), //
1150 					toLoggedBy(step.getLocator()), //
1151 					step.getResultType() == ResultType.SUCCESS);
1152 		case ASSERT_PRESENT:
1153 			return new AssertPresentLogLine( //
1154 					step.getTimeMillis(), //
1155 					"assertPresent: " + serialize(step.getLocator()) //
1156 							+ " -> " + step.getResultType(), //
1157 					toLoggedBy(step.getLocator()), //
1158 					step.getResultType() == ResultType.SUCCESS);
1159 		case ATTRIBUTE:
1160 			return new AttributeLogLine( //
1161 					step.getTimeMillis(), //
1162 					"attribute: " + step.getScope() + " " + inQuotes(step.getName()) + " " + inQuotes(step.getValue()), //
1163 					step.getScope(), //
1164 					step.getName(), //
1165 					step.getValue());
1166 		case CALL:
1167 			return new CallLogLine( //
1168 					step.getTimeMillis(), //
1169 					"call: " + step.getCallable(), //
1170 					step.getCallable());
1171 		case CLEAR:
1172 			return new ClearLogLine( //
1173 					step.getTimeMillis(), //
1174 					"clear: " + serialize(step.getLocator()), //
1175 					toLoggedBy(step.getLocator()));
1176 		case CLICK:
1177 			return new ClickLogLine( //
1178 					step.getTimeMillis(), //
1179 					"click: " + serialize(step.getLocator()), //
1180 					toLoggedBy(step.getLocator()));
1181 		case EXECUTE_SCRIPT:
1182 			return new ExecuteScriptLogLine( //
1183 					step.getTimeMillis(), //
1184 					"executeScript: " + step.getScript(), //
1185 					step.getScript());
1186 		case EXT:
1187 			return new ExtLogLine( //
1188 					step.getTimeMillis(), //
1189 					"ext: " + inQuotes(step.getNamespace()) //
1190 							+ " " + inQuotes(step.getValue()), //
1191 					step.getNamespace(), //
1192 					step.getValue());
1193 		case FAILURE:
1194 			return new FailureLogLine( //
1195 					step.getTimeMillis(), //
1196 					"failure: " + step.getMessage() //
1197 							+ " -> " + step.getResultType(), //
1198 					step.getMessage());
1199 		case GET:
1200 			return new GetLogLine( //
1201 					step.getTimeMillis(), //
1202 					"get: " + step.getUrl(), //
1203 					step.getUrl());
1204 		case MESSAGE:
1205 			return new MessageLogLine( //
1206 					step.getTimeMillis(), //
1207 					"message: " + step.getMessage(), //
1208 					step.getMessage());
1209 		case SEND_KEYS:
1210 			return new SendKeysLogLine( //
1211 					step.getTimeMillis(), //
1212 					"sendKeys: " + serialize(step.getLocator()) + " " + inQuotes(step.getValue()), //
1213 					toLoggedBy(step.getLocator()), //
1214 					step.getValue());
1215 		case SET_MASKED_VARIABLE:
1216 			return new SetMaskedVariableLogLine( //
1217 					step.getTimeMillis(), //
1218 					"setMaskedVariable: " + step.getName() + " " + step.getValue(), //
1219 					step.getName(), //
1220 					step.getValue());
1221 		case SET_VARIABLE:
1222 			return new SetVariableLogLine( //
1223 					step.getTimeMillis(), //
1224 					"setVariable: " + step.getName() + " " + step.getValue(), //
1225 					step.getName(), //
1226 					step.getValue());
1227 		case SLEEP:
1228 			return new SleepLogLine( //
1229 					step.getTimeMillis(), //
1230 					"sleep: " + step.getSeconds(), //
1231 					step.getSeconds().toString());
1232 		case SUCCESS:
1233 			return new SuccessLogLine( //
1234 					step.getTimeMillis(), //
1235 					"success: " + step.getMessage() + " -> " + ResultType.SUCCESS, //
1236 					step.getMessage());
1237 		case TAKE_SCREENSHOT:
1238 			return new TakeScreenshotLogLine( //
1239 					step.getTimeMillis(), //
1240 					"takeScreenshot: " + step.getFileName(), //
1241 					step.getFileName());
1242 		case WAIT_FOR:
1243 			return new WaitForLogLine( //
1244 					step.getTimeMillis(), //
1245 					"waitFor: " + serialize(step.getLocator()), //
1246 					toLoggedBy(step.getLocator()));
1247 		case WAIT_FOR_NOT:
1248 			return new WaitForNotLogLine( //
1249 					step.getTimeMillis(), //
1250 					"waitForNot: " + serialize(step.getLocator()), //
1251 					toLoggedBy(step.getLocator()));
1252 		default:
1253 			throw new NotImplementedException("step.type: " + step.getType());
1254 		}
1255 	}
1256 
1257 	public static Log toGuixerOutLog(
1258 		final ElaborateLog log,
1259 		final byte[] rawBytes
1260 	) {
1261 
1262 		final long runAtMs = log.getTimeMillis();
1263 
1264 		final List<LogLine> attributeLogLines = Lists.newArrayList();
1265 
1266 		for (final Attribute attribute : log.getAttributes()) {
1267 
1268 			attributeLogLines.add(new AttributeLogLine( //
1269 					runAtMs, //
1270 					"attribute: " + attribute.getScope() //
1271 							+ " " + inQuotes(attribute.getName()) //
1272 							+ " " + inQuotes(attribute.getValue()), //
1273 					attribute.getScope(), //
1274 					attribute.getName(), //
1275 					attribute.getValue()));
1276 		}
1277 
1278 		final List<LogLine> logLines = Lists.newArrayList();
1279 
1280 		if (log.getBefore() != null) {
1281 
1282 			for (final Step step : log.getBefore().getSteps()) {
1283 
1284 				logLines.add(toGuixerOutLogLine(step));
1285 			}
1286 		}
1287 
1288 		for (final Intent intent : log.getIntents()) {
1289 
1290 			dumpAsGuixerOutLogLines(intent, logLines);
1291 		}
1292 
1293 		final LogError error = log.getError();
1294 
1295 		if (error != null) {
1296 
1297 			final String trace = error.getTrace();
1298 			final String firstLine = split(trace, "\n")[0];
1299 			final String fileName = error.getScreenshot().getFileName();
1300 
1301 			logLines.add(new ErrorLogLine( //
1302 					error.getTimeMillis(), //
1303 					"error: " + firstLine, // rawText
1304 					trace));
1305 			logLines.add(new TakeScreenshotLogLine( //
1306 					error.getScreenshot().getTimeMillis(), //
1307 					"takeScreenshot: " + fileName, // rawText
1308 					fileName));
1309 		}
1310 
1311 		final Done done = log.getDone();
1312 
1313 		if (done != null) {
1314 
1315 			logLines.add(new DoneLogLine( //
1316 					done.getTimeMillis(), //
1317 					done.getMessage()));
1318 		}
1319 
1320 		return new Log() {
1321 
1322 			@Override
1323 			public byte[] getRawBytes() {
1324 				return rawBytes;
1325 			}
1326 
1327 			@Override
1328 			public String getGuixerVersion() {
1329 				return log.getGuixerVersion();
1330 			}
1331 
1332 			@Override
1333 			public String getDateAsString() {
1334 				return log.getDateAsString();
1335 			}
1336 
1337 			@Override
1338 			public String getTestClassName() {
1339 				return log.getTestClassName();
1340 			}
1341 
1342 			@Override
1343 			public String getTestClassSimpleName() {
1344 				return log.getTestClassSimpleName();
1345 			}
1346 
1347 			@Override
1348 			public String getTestMethodName() {
1349 				return log.getTestMethodName();
1350 			}
1351 
1352 			@Override
1353 			public long getTimeMillis() {
1354 				return runAtMs;
1355 			}
1356 
1357 			@Override
1358 			@Nullable
1359 			public String getScenario() {
1360 				return log.getScenario();
1361 			}
1362 
1363 			@Override
1364 			public boolean isInError() {
1365 				return log.isInError();
1366 			}
1367 
1368 			@Override
1369 			public int getSuccessCount() {
1370 				return log.getSuccessCount();
1371 			}
1372 
1373 			@Override
1374 			public int getFailureCount() {
1375 				return log.getFailureCount();
1376 			}
1377 
1378 			@Override
1379 			public DateTime getDate() {
1380 				return log.getDate();
1381 			}
1382 
1383 			@Override
1384 			public LogLine[] getAttributes() {
1385 				return Iterables.toArray(attributeLogLines, LogLine.class);
1386 			}
1387 
1388 			@Override
1389 			public LogLine[] getLogLines() {
1390 				return Iterables.toArray(logLines, LogLine.class);
1391 			}
1392 		};
1393 	}
1394 }