run.ts 8.0 KB

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