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   * Test case for {@link com.atlassian.selenium.keyboard.SeleniumTypeWriter}.
41   *
42   * @since v1.21
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 }