run.ts 7.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244
  1. /**
  2. * Copyright © 2016-2019 Threema GmbH (https://threema.ch/).
  3. *
  4. * This file is part of Threema Web.
  5. */
  6. // tslint:disable:no-reference
  7. // tslint:disable:no-console
  8. // tslint:disable:no-unused-expression
  9. /// <reference path="../../src/threema.d.ts" />
  10. import { expect } from 'chai';
  11. import { Builder, By, Key, until, WebDriver, WebElement } from 'selenium-webdriver';
  12. import * as TermColor from 'term-color';
  13. // Script arguments
  14. const browser = process.argv[2];
  15. const filterQuery = process.argv[3];
  16. // Type aliases
  17. type Testfunc = (driver: WebDriver) => void;
  18. // Shared selectors
  19. const composeArea = By.css('div.compose');
  20. const emojiKeyboard = By.css('.emoji-keyboard');
  21. const emojiTrigger = By.css('.emoji-trigger');
  22. /**
  23. * Helper function to extract text.
  24. */
  25. async function extractText(driver: WebDriver): Promise<string> {
  26. const script = `return window.composeArea.getText();`;
  27. return driver.executeScript<string>(script);
  28. }
  29. /**
  30. * Helper function to send a KeyUp event.
  31. */
  32. async function sendKeyUp(driver: WebDriver, key: string): Promise<void> {
  33. const script = `
  34. const e = document.createEvent('HTMLEvents');
  35. e.initEvent('keyup', false, true);
  36. e.key = '${key}';
  37. const element = document.querySelector("div.compose");
  38. element.dispatchEvent(e);
  39. `;
  40. return driver.executeScript<void>(script);
  41. }
  42. /**
  43. * The emoji trigger should toggle the emoji keyboard.
  44. */
  45. async function showEmojiSelector(driver: WebDriver) {
  46. // Initially not visible
  47. expect(
  48. await driver.findElement(emojiKeyboard).isDisplayed()
  49. ).to.be.false;
  50. // Show
  51. await driver.findElement(emojiTrigger).click();
  52. expect(
  53. await driver.findElement(emojiKeyboard).isDisplayed()
  54. ).to.be.true;
  55. // Hide
  56. await driver.findElement(emojiTrigger).click();
  57. expect(
  58. await driver.findElement(emojiKeyboard).isDisplayed()
  59. ).to.be.false;
  60. }
  61. /**
  62. * Insert two emoji and some text.
  63. */
  64. async function insertEmoji(driver: WebDriver) {
  65. // Show emoji keyboard
  66. await driver.findElement(emojiTrigger).click();
  67. // Insert woman zombie emoji
  68. await driver.findElement(By.css('.em[data-s=":woman_zombie:"]')).click();
  69. // Insert text
  70. await driver.findElement(composeArea).sendKeys('hi');
  71. // Insert beer
  72. await driver.findElement(By.className('em-food')).click();
  73. await driver.findElement(By.css('.em[data-s=":beers:"]')).click();
  74. // Validate emoji
  75. const emoji = await driver.findElement(composeArea).findElements(By.xpath('*'));
  76. expect(emoji.length).to.equal(2);
  77. expect(await emoji[0].getAttribute('data-c')).to.equal('1f9df-200d-2640-fe0f'); // woman zombie
  78. expect(await emoji[1].getAttribute('data-c')).to.equal('1f37b'); // clinking beer mugs
  79. // Validate text
  80. const html = await driver.findElement(composeArea).getAttribute('innerHTML');
  81. expect(/>hi<img/.test(html)).to.be.true;
  82. }
  83. /**
  84. * Insert a newline using shift-enter.
  85. */
  86. async function insertNewline(driver: WebDriver) {
  87. // Insert text
  88. await driver.findElement(composeArea).click();
  89. await driver.findElement(composeArea).sendKeys('hello');
  90. await driver.findElement(composeArea).sendKeys(Key.SHIFT, Key.ENTER);
  91. await driver.findElement(composeArea).sendKeys('threema');
  92. await driver.findElement(composeArea).sendKeys(Key.SHIFT, Key.ENTER);
  93. await driver.findElement(composeArea).sendKeys('web');
  94. const text = await extractText(driver);
  95. expect(text).to.equal('hello\nthreema\nweb');
  96. }
  97. /**
  98. * Insert an emoji after some newlines.
  99. * Regression test for #574.
  100. */
  101. async function regression574(driver: WebDriver) {
  102. // Insert text
  103. await driver.findElement(composeArea).click();
  104. await driver.findElement(composeArea).sendKeys('hello');
  105. await driver.findElement(composeArea).sendKeys(Key.SHIFT, Key.ENTER);
  106. await driver.findElement(composeArea).sendKeys('threema');
  107. await driver.findElement(composeArea).sendKeys(Key.SHIFT, Key.ENTER);
  108. await driver.findElement(composeArea).sendKeys('web');
  109. await driver.findElement(composeArea).sendKeys(Key.SHIFT, Key.ENTER);
  110. // Insert emoji
  111. await driver.findElement(emojiTrigger).click();
  112. await driver.findElement(By.css('.em[data-s=":smile:"]')).click();
  113. const text = await extractText(driver);
  114. expect(text).to.equal('hello\nthreema\nweb\n😄');
  115. }
  116. /**
  117. * Insert two emoji in the middle of existing text.
  118. * Regression test for #671.
  119. */
  120. async function regression671(driver: WebDriver) {
  121. // Insert text
  122. await driver.findElement(composeArea).click();
  123. await driver.findElement(composeArea).sendKeys('helloworld');
  124. await driver.findElement(composeArea).sendKeys(Key.LEFT, Key.LEFT, Key.LEFT, Key.LEFT, Key.LEFT);
  125. // Insert emoji
  126. await driver.findElement(emojiTrigger).click();
  127. const emoji = await driver.findElement(By.css('.em[data-s=":smile:"]'));
  128. await emoji.click();
  129. await emoji.click();
  130. const text = await extractText(driver);
  131. expect(text).to.equal('hello😄😄world');
  132. }
  133. /**
  134. * Insert two emoji between two lines of text.
  135. * Regression test for #672.
  136. */
  137. async function regression672(driver: WebDriver) {
  138. // Insert text
  139. await driver.findElement(composeArea).click();
  140. await driver.findElement(composeArea).sendKeys('hello');
  141. await driver.findElement(composeArea).sendKeys(Key.SHIFT, Key.ENTER);
  142. await driver.findElement(composeArea).sendKeys(Key.SHIFT, Key.ENTER);
  143. await driver.findElement(composeArea).sendKeys('world');
  144. await driver.findElement(composeArea).sendKeys(Key.UP);
  145. // Insert two emoji
  146. await driver.findElement(emojiTrigger).click();
  147. const emoji = await driver.findElement(By.css('.em[data-s=":tired_face:"]'));
  148. await emoji.click();
  149. await emoji.click();
  150. const text = await extractText(driver);
  151. expect(text).to.equal('hello\n😫😫\nworld');
  152. }
  153. /**
  154. * Insert emoji with a shortcode.
  155. */
  156. async function insertEmojiWithShortcode(driver: WebDriver) {
  157. // Insert text
  158. await driver.findElement(composeArea).click();
  159. await driver.findElement(composeArea).sendKeys('hello :+1:');
  160. await sendKeyUp(driver, ':');
  161. const text = await extractText(driver);
  162. expect(text).to.equal('hello 👍');
  163. }
  164. // Register tests here
  165. const TESTS: Array<[string, Testfunc]> = [
  166. ['Show and hide emoji selector', showEmojiSelector],
  167. ['Insert emoji and text', insertEmoji],
  168. ['Insert three lines of text', insertNewline],
  169. ['Regression test #574', regression574],
  170. ['Regression test #671', regression671],
  171. ['Regression test #672', regression672],
  172. ['Insert emoji through shortcode', insertEmojiWithShortcode],
  173. ];
  174. // Test runner
  175. const TEST_URL = 'http://localhost:7777/tests/ui/compose_area.html';
  176. (async function() {
  177. const driver: WebDriver = await new Builder().forBrowser(browser).build();
  178. let i = 0;
  179. let success = 0;
  180. let failed = 0;
  181. let skipped = 0;
  182. console.info('\n====== THREEMA WEB UI TESTS ======\n');
  183. if (filterQuery !== undefined) {
  184. console.info(`Filter query: "${filterQuery}"\n`);
  185. }
  186. try {
  187. for (const [name, testfunc] of TESTS) {
  188. try {
  189. if (filterQuery === undefined || name.toLowerCase().indexOf(filterQuery.toLowerCase()) !== -1) {
  190. i++;
  191. console.info(TermColor.blue(`» ${i}: Running test: ${name}`));
  192. await driver.get(TEST_URL);
  193. await testfunc(driver);
  194. success++;
  195. } else {
  196. skipped++;
  197. }
  198. } catch (e) {
  199. console.error(TermColor.red(`\nTest failed:`));
  200. console.error(e);
  201. failed++;
  202. }
  203. }
  204. } finally {
  205. await driver.quit();
  206. }
  207. const colorFunc = failed > 0 ? TermColor.red : TermColor.green;
  208. console.info(colorFunc(`\nSummary: ${i} tests run, ${success} succeeded, ${failed} failed, ${skipped} skipped`));
  209. process.exit(failed > 0 ? 1 : 0);
  210. })();