1 package com.atlassian.webtest.ui.keys;
2
3 import com.atlassian.selenium.SeleniumClient;
4 import com.atlassian.selenium.keyboard.SeleniumTypeWriter;
5 import com.google.common.base.Function;
6 import com.google.common.base.Preconditions;
7 import com.google.common.collect.Collections2;
8 import com.google.common.collect.Iterators;
9 import org.junit.Before;
10 import org.junit.Test;
11 import org.mockito.invocation.InvocationOnMock;
12 import org.mockito.stubbing.Answer;
13
14 import java.awt.event.KeyEvent;
15 import java.util.Collection;
16 import java.util.EnumSet;
17 import java.util.Iterator;
18 import java.util.LinkedList;
19 import java.util.List;
20 import java.util.Set;
21
22 import static com.atlassian.webtest.ui.keys.KeyEventType.KEYDOWN;
23 import static com.atlassian.webtest.ui.keys.KeyEventType.KEYPRESS;
24 import static com.atlassian.webtest.ui.keys.KeyEventType.KEYUP;
25 import static com.atlassian.webtest.ui.keys.Sequences.chars;
26 import static com.atlassian.webtest.ui.keys.Sequences.charsBuilder;
27 import static com.atlassian.webtest.ui.keys.Sequences.keysBuilder;
28 import static org.junit.Assert.assertEquals;
29 import static org.junit.Assert.assertTrue;
30 import static org.mockito.Matchers.anyChar;
31 import static org.mockito.Matchers.anyCollectionOf;
32 import static org.mockito.Matchers.anyInt;
33 import static org.mockito.Matchers.anyString;
34 import static org.mockito.Mockito.doAnswer;
35 import static org.mockito.Mockito.mock;
36 import static org.mockito.Mockito.verify;
37 import static org.mockito.Mockito.when;
38
39
40
41
42
43
44 public class TestSeleniumTypeWriter
45 {
46 private static final String TEST_LOCATOR = "id=test";
47 private static final String TEST_LOCATOR_WITH_QUOTES = "css=input[name='inquotes']";
48 private static final int CORRECT_ENTER = 13;
49
50 private KeyPressVerifyingMock keyPressMock;
51 private SeleniumTypeWriter writer;
52
53
54 @Before
55 public void setUpMocks()
56 {
57 keyPressMock = new KeyPressVerifyingMock();
58 writer = new SeleniumTypeWriter(keyPressMock.mockClient(), TEST_LOCATOR, TypeMode.TYPE);
59 }
60
61 @Test
62 public void shouldInsertWithEvent()
63 {
64 writer.type(charsBuilder("blah").typeMode(TypeMode.INSERT_WITH_EVENT).build());
65 keyPressMock.verifyTyped(TEST_LOCATOR, "bla");
66 keyPressMock.verifyChars(TEST_LOCATOR, "h", KeyEventType.ALL);
67 keyPressMock.verifyNoMore();
68 }
69
70
71 @Test
72 public void fullTypeShouldUseAllKeyEventsByDefault()
73 {
74 writer.type(chars("blah"));
75 keyPressMock.verifyChars(TEST_LOCATOR, "blah", KeyEventType.ALL);
76 keyPressMock.verifyNoMore();
77 }
78
79 @Test
80 public void shouldSupportCustomKeySequenceImplementations()
81 {
82 writer.type(new CustomKeySequence(chars("test")));
83 keyPressMock.verifyChars(TEST_LOCATOR, "test", KeyEventType.ALL);
84 keyPressMock.verifyNoMore();
85 }
86
87 @Test
88 public void shouldHandleNonEditableElements()
89 {
90 when(keyPressMock.mockClient().getValue(TEST_LOCATOR)).thenThrow(new IllegalStateException("I'm not editable"));
91 when(keyPressMock.mockClient().isElementPresent(TEST_LOCATOR)).thenReturn(true);
92 when(keyPressMock.mockClient().getEval("this.getTagName('" + TEST_LOCATOR + "')")).thenReturn("body");
93 writer.type(chars("test"));
94 keyPressMock.verifyChars(TEST_LOCATOR, "test", EnumSet.allOf(KeyEventType.class));
95 keyPressMock.verifyNoMore();
96 verify(keyPressMock.mockClient()).getEval("this.getTagName('" + TEST_LOCATOR + "')");
97 }
98
99 @Test
100 public void shouldTypeSpecifiedCharEventsInFullTypeMode()
101 {
102 writer.type(charsBuilder("blah").keyEvents(KEYDOWN, KEYPRESS).build());
103 keyPressMock.verifyChars(TEST_LOCATOR, "blah", EnumSet.of(KEYDOWN, KEYPRESS));
104 keyPressMock.verifyNoMore();
105 }
106
107 @Test
108 public void shouldTypeSpecifiedKeyEventsInFullTypeMode()
109 {
110 writer.type(SpecialKeys.ARROW_DOWN.withEvents(KEYDOWN, KEYPRESS));
111 keyPressMock.verifyKeys(TEST_LOCATOR, EnumSet.of(KEYDOWN, KEYPRESS), KeyEvent.VK_DOWN);
112 keyPressMock.verifyNoMore();
113 }
114
115 @Test
116 public void shouldTypeSpecifiedKeyEventsInMixedSequenceAndFullTypeMode()
117 {
118 KeySequence input = new KeySequenceBuilder("abc").append(SpecialKeys.ENTER).append("def")
119 .keyEvents(KEYDOWN, KEYUP).build();
120 writer.type(input);
121 keyPressMock.verifyChars(TEST_LOCATOR, "abc", EnumSet.of(KEYDOWN, KEYUP));
122 keyPressMock.verifyKeys(TEST_LOCATOR, EnumSet.of(KEYDOWN, KEYUP), CORRECT_ENTER);
123 keyPressMock.verifyChars(TEST_LOCATOR, "def", EnumSet.of(KEYDOWN, KEYUP));
124 keyPressMock.verifyNoMore();
125 }
126
127 @Test
128 public void shouldTypeCharsInInsertTypeMode()
129 {
130 writer.type(charsBuilder("blah").keyEvents(KEYDOWN, KEYPRESS).typeMode(TypeMode.INSERT)
131 .build());
132 keyPressMock.verifyTyped(TEST_LOCATOR, "blah");
133 keyPressMock.verifyNoMore();
134 }
135
136 @Test
137 public void shouldTypeSpecifiedKeyEventsInInsertTypeMode()
138 {
139 writer.type(keysBuilder(SpecialKeys.ARROW_DOWN).keyEvents(KEYDOWN, KEYPRESS)
140 .typeMode(TypeMode.INSERT).build());
141 keyPressMock.verifyKeys(TEST_LOCATOR, EnumSet.of(KEYDOWN, KEYPRESS), KeyEvent.VK_DOWN);
142 keyPressMock.verifyNoMore();
143 }
144
145 @Test
146 public void shouldTypeSpecifiedKeyEventsInMixedSequenceAndInsertTypeMode()
147 {
148 KeySequence input = new KeySequenceBuilder("abc").append(SpecialKeys.ENTER).append("def")
149 .keyEvents(KEYDOWN, KEYUP).typeMode(TypeMode.INSERT).build();
150 writer.type(input);
151 keyPressMock.verifyTyped(TEST_LOCATOR, "abc");
152 keyPressMock.verifyKeys(TEST_LOCATOR, EnumSet.of(KEYDOWN, KEYUP), CORRECT_ENTER);
153 keyPressMock.verifyTyped(TEST_LOCATOR, "def");
154 keyPressMock.verifyNoMore();
155 }
156
157 @Test
158 public void shouldTypeCharsInInsertWithEventTypeMode()
159 {
160 writer.type(charsBuilder("blah").keyEvents(KEYDOWN, KEYPRESS).typeMode(TypeMode.INSERT_WITH_EVENT)
161 .build());
162 keyPressMock.verifyTyped(TEST_LOCATOR, "bla");
163 keyPressMock.verifyChars(TEST_LOCATOR, "h", EnumSet.of(KEYDOWN, KEYPRESS));
164 keyPressMock.verifyNoMore();
165 }
166
167 @Test
168 public void shouldTypeSpecifiedKeyEventsInInsertWithEventTypeMode()
169 {
170 writer.type(keysBuilder(SpecialKeys.ARROW_DOWN).keyEvents(KEYDOWN, KEYPRESS).typeMode(TypeMode.INSERT_WITH_EVENT)
171 .build());
172 keyPressMock.verifyKeys(TEST_LOCATOR, EnumSet.of(KEYDOWN, KEYPRESS), KeyEvent.VK_DOWN);
173 keyPressMock.verifyNoMore();
174 }
175
176 @Test
177 public void shouldTypeSpecifiedKeyEventsInMixedSequenceAndInsertWithEventTypeMode()
178 {
179 KeySequence input = new KeySequenceBuilder("abc").append(SpecialKeys.ENTER).append("def")
180 .keyEvents(KEYDOWN, KEYUP).typeMode(TypeMode.INSERT_WITH_EVENT).build();
181 writer.type(input);
182 keyPressMock.verifyTyped(TEST_LOCATOR, "abc");
183 keyPressMock.verifyKeys(TEST_LOCATOR, EnumSet.of(KEYDOWN, KEYUP), CORRECT_ENTER);
184 keyPressMock.verifyTyped(TEST_LOCATOR, "de");
185 keyPressMock.verifyChars(TEST_LOCATOR, "f", EnumSet.of(KEYDOWN, KEYUP));
186 keyPressMock.verifyNoMore();
187 }
188
189 @Test
190 public void shouldUserCorrectKeyForEnter()
191 {
192 writer.type(SpecialKeys.ENTER);
193 keyPressMock.verifyKeys(TEST_LOCATOR, KeyEventType.ALL, CORRECT_ENTER);
194 keyPressMock.verifyNoMore();
195 }
196
197
198 @Test
199 public void shouldTypeSpecifiedKeyEventsInMixedSequenceAndInsertWithEventTypeModeWithKeyAtTheEndHAHAHA()
200 {
201 KeySequence input = new KeySequenceBuilder("abc").append(SpecialKeys.ENTER).append("def")
202 .append(SpecialKeys.ESC).keyEvents(KEYDOWN, KEYPRESS).typeMode(TypeMode.INSERT_WITH_EVENT)
203 .build();
204 writer.type(input);
205 keyPressMock.verifyTyped(TEST_LOCATOR, "abc");
206 keyPressMock.verifyKeys(TEST_LOCATOR, EnumSet.of(KEYDOWN, KEYPRESS), CORRECT_ENTER);
207 keyPressMock.verifyTyped(TEST_LOCATOR, "def");
208 keyPressMock.verifyKeys(TEST_LOCATOR, EnumSet.of(KEYDOWN, KEYPRESS), KeyEvent.VK_ESCAPE);
209 keyPressMock.verifyNoMore();
210 }
211
212 @Test
213 public void shouldTypeAllSpecialKeys()
214 {
215 KeySequence input = new KeySequenceBuilder().append(allSpecialKeys()).keyEvents(KEYDOWN, KEYPRESS)
216 .typeMode(TypeMode.INSERT_WITH_EVENT).build();
217 writer.type(input);
218 keyPressMock.verifyKeys(TEST_LOCATOR, EnumSet.of(KEYDOWN, KEYPRESS),
219 CORRECT_ENTER,
220 KeyEvent.VK_ESCAPE,
221 KeyEvent.VK_BACK_SPACE,
222 KeyEvent.VK_DELETE,
223 KeyEvent.VK_SPACE,
224 KeyEvent.VK_LEFT,
225 KeyEvent.VK_RIGHT,
226 KeyEvent.VK_UP,
227 KeyEvent.VK_DOWN);
228 keyPressMock.verifyNoMore();
229 }
230
231 @Test
232 public void shouldHandleZeroLengthSequenceInInsertMode()
233 {
234 writer.type(charsBuilder("").typeMode(TypeMode.INSERT).build());
235 keyPressMock.verifyTyped(TEST_LOCATOR, "");
236 keyPressMock.verifyNoMore();
237 }
238
239 @Test
240 public void shouldHandleZeroLengthSequenceInInsertWithEventMode()
241 {
242 writer.type(charsBuilder("").typeMode(TypeMode.INSERT_WITH_EVENT).build());
243 keyPressMock.verifyTyped(TEST_LOCATOR, "");
244 keyPressMock.verifyNoMore();
245 }
246
247 @Test
248 public void shouldHandleZeroLengthSequenceInFullEventTypeMode()
249 {
250 writer.type(charsBuilder("").typeMode(TypeMode.TYPE).build());
251 keyPressMock.verifyNoMore();
252 }
253
254 @Test
255 public void shouldPreserveExistingText()
256 {
257 when(keyPressMock.mockClient().getValue(TEST_LOCATOR)).thenReturn("existing");
258 when(keyPressMock.mockClient().isElementPresent(TEST_LOCATOR)).thenReturn(true);
259 when(keyPressMock.mockClient().getEval("this.getTagName('" + TEST_LOCATOR + "')")).thenReturn("input");
260 writer.type(charsBuilder("").typeMode(TypeMode.INSERT).build());
261 keyPressMock.verifyTyped(TEST_LOCATOR, "existing");
262 keyPressMock.verifyNoMore();
263
264 }
265
266 @Test
267 public void shouldNotPreserveExistingTextOnClear()
268 {
269 when(keyPressMock.mockClient().getValue(TEST_LOCATOR)).thenReturn("existing");
270 when(keyPressMock.mockClient().isElementPresent(TEST_LOCATOR)).thenReturn(true);
271 when(keyPressMock.mockClient().getEval("this.getTagName('" + TEST_LOCATOR + "')")).thenReturn("input");
272 writer.clear();
273 keyPressMock.verifyTyped(TEST_LOCATOR, "");
274 keyPressMock.verifyNoMore();
275
276 }
277
278 private static Collection<Key> allSpecialKeys()
279 {
280 return Collections2.transform(EnumSet.allOf(SpecialKeys.class), new Function<SpecialKeys, Key>()
281 {
282 public Key apply(SpecialKeys input)
283 {
284 return input;
285 }
286 });
287 }
288
289 private static class CustomKeySequence implements KeySequence
290 {
291 private final KeySequence real;
292
293 public CustomKeySequence(final KeySequence real)
294 {
295 this.real = real;
296 }
297
298 public List<Key> keys()
299 {
300 return real.keys();
301 }
302
303 public Set<ModifierKey> withPressed()
304 {
305 return real.withPressed();
306 }
307 }
308
309 private static class KeyPressVerifyingMock
310 {
311 private final SeleniumClient mockClient;
312 private final List<KeyInteraction> keyPresses = new LinkedList<KeyInteraction>();
313 private Iterator<KeyInteraction> current;
314
315 public KeyPressVerifyingMock()
316 {
317 this.mockClient = mock(SeleniumClient.class);
318 doAnswer(new Answer<Void>()
319 {
320 public Void answer(InvocationOnMock invocation) throws Throwable
321 {
322 checkNoVerification();
323 keyPresses.add(new KeyInteraction(targetArg(invocation), charArg(invocation), eventsArg(invocation)));
324 return null;
325 }
326 }).when(mockClient).simulateKeyPressForCharacter(anyString(), anyChar(), anyCollectionOf(KeyEventType.class));
327 doAnswer(new Answer<Void>()
328 {
329 public Void answer(InvocationOnMock invocation) throws Throwable
330 {
331 checkNoVerification();
332 keyPresses.add(new KeyInteraction(targetArg(invocation), keyArg(invocation), eventsArg(invocation)));
333 return null;
334 }
335 }).when(mockClient).simulateKeyPressForSpecialKey(anyString(), anyInt(), anyCollectionOf(KeyEventType.class));
336 doAnswer(new Answer<Void>()
337 {
338 public Void answer(InvocationOnMock invocation) throws Throwable
339 {
340 checkNoVerification();
341 keyPresses.add(new KeyInteraction(targetArg(invocation), typedArg(invocation)));
342 return null;
343 }
344 }).when(mockClient).type(anyString(), anyString());
345 }
346
347 private void checkNoVerification()
348 {
349 if (isVerifying())
350 {
351 throw new IllegalStateException("Already verifying");
352 }
353 }
354
355 static String targetArg(InvocationOnMock invocation)
356 {
357 return (String) invocation.getArguments()[0];
358 }
359
360 static char charArg(InvocationOnMock invocation)
361 {
362 return (Character) invocation.getArguments()[1];
363 }
364
365 static int keyArg(InvocationOnMock invocation)
366 {
367 return (Integer) invocation.getArguments()[1];
368 }
369
370 static String typedArg(InvocationOnMock invocation)
371 {
372 return (String) invocation.getArguments()[1];
373 }
374
375 @SuppressWarnings ({ "unchecked" })
376 static Collection<KeyEventType> eventsArg(InvocationOnMock invocation)
377 {
378 return (Collection<KeyEventType>) invocation.getArguments()[2];
379 }
380
381 SeleniumClient mockClient()
382 {
383 return mockClient;
384 }
385
386 private void startVerification()
387 {
388 if (current == null)
389 {
390 current = keyPresses.iterator();
391 }
392 }
393
394 boolean isVerifying()
395 {
396 return current != null;
397 }
398
399 void verifyChars(String target, String chars, Set<KeyEventType> events)
400 {
401 startVerification();
402 for (char character : chars.toCharArray())
403 {
404 nextInteraction().verify(target, character, events);
405 }
406 }
407
408 void verifyKeys(String target, Set<KeyEventType> events, int... keys)
409 {
410 startVerification();
411 for (int key : keys)
412 {
413 nextInteraction().verify(target, key, events);
414 }
415 }
416
417 void verifyTyped(String target, String typed)
418 {
419 startVerification();
420 nextInteraction().verify(target, typed);
421 }
422
423 KeyInteraction nextInteraction()
424 {
425 if (current.hasNext())
426 {
427 return current.next();
428 }
429 else
430 {
431 throw new IllegalStateException("No more recorded key presses");
432 }
433 }
434
435 void verifyNoMore()
436 {
437 startVerification();
438 if (current.hasNext())
439 {
440 throw new AssertionError("More events available:" + Iterators.toString(current));
441 }
442 }
443
444 }
445
446 private static class KeyInteraction
447 {
448 private final String target;
449 private final String typed;
450 private final char character;
451 private final int key;
452 private final EnumSet<KeyEventType> keyEvents;
453
454 KeyInteraction(String target, char character, Collection<KeyEventType> events)
455 {
456 this.target = target;
457 this.typed = null;
458 this.character = character;
459 this.key = -1;
460 this.keyEvents = EnumSet.copyOf(events);
461 }
462
463 KeyInteraction(String target, int key, Collection<KeyEventType> events)
464 {
465 Preconditions.checkArgument(key > 0);
466 this.target = target;
467 this.typed = null;
468 this.character = 0;
469 this.key = key;
470 this.keyEvents = EnumSet.copyOf(events);
471 }
472
473 KeyInteraction(String target, String typed)
474 {
475 Preconditions.checkNotNull(typed);
476 this.target = target;
477 this.typed = typed;
478 this.character = 0;
479 this.key = -1;
480 this.keyEvents = null;
481 }
482
483 boolean isTyped()
484 {
485 return !isKey() && typed != null;
486 }
487
488 boolean isChar()
489 {
490 return !isKey() && !isTyped();
491 }
492
493 boolean isKey()
494 {
495 return key > 0;
496 }
497
498 void verify(String target, String typed)
499 {
500 assertTrue("Not a type interaction:" + this, isTyped());
501 assertEquals(target, this.target);
502 assertEquals(typed, this.typed);
503 }
504
505 void verify(String target, char character, Set<KeyEventType> events)
506 {
507 assertTrue("Not a char event: " + this, isChar());
508 assertEquals(target, this.target);
509 assertEquals(character, this.character);
510 assertEquals(events, this.keyEvents);
511 }
512
513 void verify(String target, int key, Set<KeyEventType> events)
514 {
515 assertTrue("Not a key event: " + this, isKey());
516 assertEquals(target, this.target);
517 assertEquals(key, this.key);
518 assertEquals(events, this.keyEvents);
519 }
520
521 @Override
522 public String toString()
523 {
524 if (isChar())
525 {
526 return "Character Key Press [target=" + target + ",character=" + character + ",events=" + keyEvents + "]";
527 }
528 if (isTyped())
529 {
530 return "Type Interaction [target=" + target + ",typed=" + typed + "]";
531 }
532 if (isKey())
533 {
534 return "Special Key Press [target=" + target + ",key=" + key + ",events=" + keyEvents + "]";
535 }
536 throw new AssertionError("???");
537 }
538 }
539 }