run.ts 8.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263
  1. /**
  2. * Copyright © 2016-2020 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.id('composeDiv');
  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.get_text();`;
  27. return driver.executeScript<string>(script);
  28. }
  29. /**
  30. * Helper function to send a KeyDown event.
  31. */
  32. async function sendKeyDown(driver: WebDriver, key: string): Promise<void> {
  33. const script = `
  34. const e = document.createEvent('HTMLEvents');
  35. e.initEvent('keydown', 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. * Helper function to send a KeyUp event.
  44. */
  45. async function sendKeyUp(driver: WebDriver, key: string): Promise<void> {
  46. const script = `
  47. const e = document.createEvent('HTMLEvents');
  48. e.initEvent('keyup', false, true);
  49. e.key = '${key}';
  50. const element = document.querySelector("div.compose");
  51. element.dispatchEvent(e);
  52. `;
  53. return driver.executeScript<void>(script);
  54. }
  55. /**
  56. * The emoji trigger should toggle the emoji keyboard.
  57. */
  58. async function buttonTogglesEmojiSelector(driver: WebDriver) {
  59. // Initially not visible
  60. expect(
  61. await driver.findElement(emojiKeyboard).isDisplayed(),
  62. ).to.be.false;
  63. // Show
  64. await driver.findElement(emojiTrigger).click();
  65. expect(
  66. await driver.findElement(emojiKeyboard).isDisplayed(),
  67. ).to.be.true;
  68. // Hide
  69. await driver.findElement(emojiTrigger).click();
  70. expect(
  71. await driver.findElement(emojiKeyboard).isDisplayed(),
  72. ).to.be.false;
  73. }
  74. /**
  75. * Insert two emoji and some text.
  76. */
  77. async function insertEmoji(driver: WebDriver) {
  78. // Show emoji keyboard
  79. await driver.findElement(emojiTrigger).click();
  80. // Insert woman zombie emoji
  81. await driver.findElement(By.css('.em[data-s=":woman_zombie:"]')).click();
  82. // Insert text
  83. await driver.findElement(composeArea).sendKeys('hi');
  84. // Insert beer
  85. await driver.findElement(By.className('em-food')).click();
  86. const elem = await driver.findElement(By.css('.em[data-s=":beers:"]'));
  87. await elem.click();
  88. // Validate emoji
  89. const emoji = await driver.findElement(composeArea).findElements(By.xpath('*'));
  90. expect(emoji.length).to.equal(2);
  91. expect(await emoji[0].getAttribute('data-c')).to.equal('1f9df-200d-2640-fe0f'); // woman zombie
  92. expect(await emoji[1].getAttribute('data-c')).to.equal('1f37b'); // clinking beer mugs
  93. // Validate text
  94. const html = await driver.findElement(composeArea).getAttribute('innerHTML');
  95. expect(/>hi<img/.test(html)).to.be.true;
  96. }
  97. /**
  98. * Insert a newline using shift-enter.
  99. */
  100. async function insertNewline(driver: WebDriver) {
  101. // Insert text
  102. await driver.findElement(composeArea).click();
  103. await driver.findElement(composeArea).sendKeys('hello');
  104. await driver.findElement(composeArea).sendKeys(Key.SHIFT, Key.ENTER);
  105. await driver.findElement(composeArea).sendKeys('threema');
  106. await driver.findElement(composeArea).sendKeys(Key.SHIFT, Key.ENTER);
  107. await driver.findElement(composeArea).sendKeys('web');
  108. const text = await extractText(driver);
  109. expect(text).to.equal('hello\nthreema\nweb');
  110. }
  111. /**
  112. * Insert an emoji after some newlines.
  113. * Regression test for #574.
  114. */
  115. async function regression574(driver: WebDriver) {
  116. // Insert text
  117. await driver.findElement(composeArea).click();
  118. await driver.findElement(composeArea).sendKeys('hello');
  119. await driver.findElement(composeArea).sendKeys(Key.SHIFT, Key.ENTER);
  120. await driver.findElement(composeArea).sendKeys('threema');
  121. await driver.findElement(composeArea).sendKeys(Key.SHIFT, Key.ENTER);
  122. await driver.findElement(composeArea).sendKeys('web');
  123. await driver.findElement(composeArea).sendKeys(Key.SHIFT, Key.ENTER);
  124. // Insert emoji
  125. await driver.findElement(emojiTrigger).click();
  126. await driver.findElement(By.css('.em[data-s=":happy:"]')).click();
  127. const text = await extractText(driver);
  128. expect(text).to.equal('hello\nthreema\nweb\n😄');
  129. }
  130. /**
  131. * Insert two emoji in the middle of existing text.
  132. * Regression test for #671.
  133. */
  134. async function regression671(driver: WebDriver) {
  135. // Insert text
  136. await driver.findElement(composeArea).click();
  137. await driver.findElement(composeArea).sendKeys('helloworld');
  138. await driver.findElement(composeArea).sendKeys(Key.LEFT, Key.LEFT, Key.LEFT, Key.LEFT, Key.LEFT);
  139. // Insert emoji
  140. await driver.findElement(emojiTrigger).click();
  141. const emoji = await driver.findElement(By.css('.em[data-s=":happy:"]'));
  142. await emoji.click();
  143. await emoji.click();
  144. const text = await extractText(driver);
  145. expect(text).to.equal('hello😄😄world');
  146. }
  147. /**
  148. * Insert two emoji between two lines of text.
  149. * Regression test for #672.
  150. */
  151. async function regression672(driver: WebDriver) {
  152. // Insert text
  153. await driver.findElement(composeArea).click();
  154. await driver.findElement(composeArea).sendKeys('hello');
  155. await driver.findElement(composeArea).sendKeys(Key.SHIFT, Key.ENTER);
  156. await driver.findElement(composeArea).sendKeys(Key.SHIFT, Key.ENTER);
  157. await driver.findElement(composeArea).sendKeys('world');
  158. await driver.findElement(composeArea).sendKeys(Key.UP);
  159. // Insert two emoji
  160. await driver.findElement(emojiTrigger).click();
  161. const emoji = await driver.findElement(By.css('.em[data-s=":tired:"]'));
  162. await emoji.click();
  163. await emoji.click();
  164. const text = await extractText(driver);
  165. expect(text).to.equal('hello\n😫😫\nworld');
  166. }
  167. /**
  168. * Insert emoji with a shortcode.
  169. */
  170. async function insertEmojiWithShortcode(driver: WebDriver) {
  171. // Insert text
  172. await driver.findElement(composeArea).click();
  173. await driver.findElement(composeArea).sendKeys('hello :+1');
  174. await sendKeyDown(driver, ':');
  175. const text = await extractText(driver);
  176. expect(text).to.equal('hello 👍️');
  177. }
  178. // Register tests here
  179. const TESTS: Array<[string, Testfunc]> = [
  180. ['Show and hide emoji selector', buttonTogglesEmojiSelector],
  181. ['Insert emoji and text', insertEmoji],
  182. ['Insert three lines of text', insertNewline],
  183. ['Regression test #574', regression574],
  184. ['Regression test #671', regression671],
  185. ['Regression test #672', regression672],
  186. ['Insert emoji through shortcode', insertEmojiWithShortcode],
  187. ];
  188. // Test runner
  189. const TEST_URL = 'http://localhost:7777/tests/ui/compose_area.html';
  190. (async function() {
  191. const driver: WebDriver = await new Builder().forBrowser(browser).build();
  192. driver.manage().setTimeouts({implicit: 1000, pageLoad: 30000, script: 30000});
  193. let i = 0;
  194. let success = 0;
  195. let failed = 0;
  196. let skipped = 0;
  197. console.info('\n====== THREEMA WEB UI TESTS ======\n');
  198. if (filterQuery !== undefined) {
  199. console.info(`Filter query: "${filterQuery}"\n`);
  200. }
  201. try {
  202. // Initial pageload to ensure bundles are generated
  203. await driver.get(TEST_URL);
  204. for (const [name, testfunc] of TESTS) {
  205. try {
  206. if (filterQuery === undefined || name.toLowerCase().indexOf(filterQuery.toLowerCase()) !== -1) {
  207. i++;
  208. console.info(TermColor.blue(`» ${i}: Running test: ${name}`));
  209. await driver.get(TEST_URL);
  210. await testfunc(driver);
  211. success++;
  212. } else {
  213. skipped++;
  214. }
  215. } catch (e) {
  216. console.error(TermColor.red(`\nTest failed:`));
  217. console.error(e);
  218. failed++;
  219. }
  220. }
  221. } finally {
  222. await driver.quit();
  223. }
  224. const colorFunc = failed > 0 ? TermColor.red : TermColor.green;
  225. console.info(colorFunc(`\nSummary: ${i} tests run, ${success} succeeded, ${failed} failed, ${skipped} skipped`));
  226. process.exit(failed > 0 ? 1 : 0);
  227. })();