Browse Source

Switch from testcafe to selenium

The reason for this is that testcafe cannot properly simulate some
keystrokes like SHIFT+ENTER.

Selenium requires some binary components, but that is OK since they can
be installed via npm.
Danilo Bargen 6 năm trước cách đây
mục cha
commit
a80875f59a
6 tập tin đã thay đổi với 241 bổ sung707 xóa
  1. 4 4
      .circleci/config.yml
  2. 3 3
      README.md
  3. 107 655
      package-lock.json
  4. 10 4
      package.json
  5. 4 3
      tests/ui/run.sh
  6. 113 38
      tests/ui/run.ts

+ 4 - 4
.circleci/config.yml

@@ -22,28 +22,28 @@ jobs:
     steps: *test-steps
     environment:
       BUILDTARGET: build:unittests
-      TESTTARGET: test:unittests
+      TESTTARGET: test:unit
   unittest-node10:
     docker:
       - image: circleci/node:10-browsers
     steps: *test-steps
     environment:
       BUILDTARGET: build:unittests
-      TESTTARGET: test:unittests
+      TESTTARGET: test:unit
   uitest-firefox:
     docker:
       - image: circleci/node:10-browsers
     steps: *test-steps
     environment:
       BUILDTARGET: build:uitests
-      TESTTARGET: test:uitests firefox:headless
+      TESTTARGET: test:ui firefox
   uitest-chrome:
     docker:
       - image: circleci/node:10-browsers
     steps: *test-steps
     environment:
       BUILDTARGET: build:uitests
-      TESTTARGET: test:uitests firefox:headless
+      TESTTARGET: test:ui chrome
   lint:
     docker:
       - image: circleci/node:8-browsers

+ 3 - 3
README.md

@@ -71,12 +71,12 @@ To run unit tests:
 
 To run UI tests:
 
-    npm run test:uitests <browser>
+    npm run test:ui <browser>
 
 For example:
 
-    npm run test:uitests firefox
-    npm run test:uitests chromium:headless
+    npm run test:ui firefox
+    npm run test:ui chrome
 
 To run linting checks:
 

Những thai đổi đã bị hủy bỏ vì nó quá lớn
+ 107 - 655
package-lock.json


+ 10 - 4
package.json

@@ -14,9 +14,9 @@
     "serve:live": "echo 'NOTE: serve:live command has been renamed to devserver'",
     "devserver": "npm run build:css && concurrently --kill-others --names \"css,server\" -p name \"npm run build:css:watch\" \"budo src/app.ts:dist/app.js -d public -d . -d src --live -- -d -p tsify -t [ babelify --presets [ es2015 ] --extensions .ts ]\"",
     "testserver": "budo -d public -d . -d src -p 7777",
-    "test": "echo -e 'NOTE: Use either \"npm test:unittests\" or \"npm test:uitests\"\n' && exit 1",
-    "test:unittests": "npm run build:unittests && karma start --single-run --log-level=debug --colors",
-    "test:uitests": "npm run build:uitests && bash tests/ui/run.sh",
+    "test": "echo -e 'NOTE: Use either \"npm test:unit\" or \"npm test:ui\"\n' && exit 1",
+    "test:unit": "npm run build:unittests && karma start --single-run --log-level=debug --colors",
+    "test:ui": "npm run build:uitests && bash tests/ui/run.sh",
     "lint": "tslint -c tslint.json --project tsconfig.json --exclude \"**/src/config.ts\"",
     "clean": "rm -rf js/ build/ dist/app*"
   },
@@ -72,17 +72,23 @@
     "webrtc-adapter": "^6.3.2"
   },
   "devDependencies": {
+    "@types/chai": "^4.1.7",
     "@types/jasmine": "^2.8.8",
+    "@types/selenium-webdriver": "^3.0.13",
     "angular-mocks": "^1.7.3",
     "budo": "^11",
+    "chai": "^4.2.0",
     "concurrently": "~3.3.0",
+    "geckodriver": "^1.14.1",
     "jasmine": "^3.2.0",
     "jasmine-core": "^3.2.1",
     "karma": "^2.0.5",
     "karma-chrome-launcher": "^2.2.0",
     "karma-firefox-launcher": "^1.1.0",
     "karma-jasmine": "^1.1.2",
-    "testcafe": "^0.23.2",
+    "selenium-webdriver": "^4.0.0-alpha.1",
+    "term-color": "^1.0.1",
+    "ts-node": "^7.0.1",
     "tslint": "~5.10"
   }
 }

+ 4 - 3
tests/ui/run.sh

@@ -4,13 +4,14 @@ if [ $# -lt 1 ]; then
     exit 1
 fi
 
-bin_path=node_modules/.bin
+export PATH=$PATH:"$(pwd)/node_modules/.bin/"
+
 browser=$1
 shift
 
-$bin_path/concurrently \
+concurrently \
     --kill-others \
     -s first \
     --names \"server,test\" \
     "npm run testserver" \
-    "$bin_path/testcafe $browser tests/ui/run.ts $*"
+    "ts-node --skip-project -O '{\"target\": \"ES2015\"}' tests/ui/run.ts $browser"

+ 113 - 38
tests/ui/run.ts

@@ -3,59 +3,134 @@
  *
  * This file is part of Threema Web.
  */
+
+// tslint:disable:no-reference
+// tslint:disable:no-console
 // tslint:disable:no-unused-expression
 
-import { Selector, ClientFunction } from 'testcafe';
+/// <reference path="../../src/threema.d.ts" />
+
+import { expect } from 'chai';
+import { Builder, By, Key, until, WebDriver, WebElement } from 'selenium-webdriver';
+import * as TermColor from 'term-color';
 
-// NOTE: These tests use test cafe.
-// See http://devexpress.github.io/testcafe/documentation/getting-started/ for
-// documentation on how to write UI tests.
+import { extractText } from '../../src/helpers';
 
-fixture `Compose Area`
-    .page `http://localhost:7777/tests/ui/compose_area.html`;
+// Script arguments
+const browser = process.argv[2];
 
-test('Show and hide emoji selector', async (t) => {
-    const keyboard = await Selector('.emoji-keyboard');
+// Type aliases
+type Testfunc = (driver: WebDriver) => void;
 
-    // Not visible initially
-    await t.expect(keyboard.visible).eql(false);
+// Shared selectors
+const composeArea = By.css('div.compose');
+const emojiKeyboard = By.css('.emoji-keyboard');
+const emojiTrigger = By.css('.emoji-trigger');
+
+/**
+ * The emoji trigger should toggle the emoji keyboard.
+ */
+async function showEmojiSelector(driver: WebDriver) {
+    // Initially not visible
+    expect(
+        await driver.findElement(emojiKeyboard).isDisplayed()
+    ).to.be.false;
 
     // Show
-    await t.click('.emoji-trigger');
+    await driver.findElement(emojiTrigger).click();
 
-    // Visible
-    await t.expect(keyboard.visible).eql(true);
+    expect(
+        await driver.findElement(emojiKeyboard).isDisplayed()
+    ).to.be.true;
 
     // Hide
-    await t.click('.emoji-trigger');
+    await driver.findElement(emojiTrigger).click();
 
-    // Visible
-    await t.expect(keyboard.visible).eql(false);
-});
+    expect(
+        await driver.findElement(emojiKeyboard).isDisplayed()
+    ).to.be.false;
+}
 
-test('Insert emoji', async (t) => {
+/**
+ * Insert two emoji and some text.
+ */
+async function insertEmoji(driver: WebDriver) {
     // Show emoji keyboard
-    await t.click('.emoji-trigger');
+    await driver.findElement(emojiTrigger).click();
 
     // Insert woman zombie emoji
-    await t.click('.e1._1f9df-2640');
+    await driver.findElement(By.css('.e1._1f9df-2640')).click();
+
+    // Insert text
+    await driver.findElement(composeArea).sendKeys('hi');
 
     // Insert beer
-    await t.click('.e1-food').click('.e1._1f37b');
-
-    // Ensure both have been inserted
-    const getChildNodeCount = await ClientFunction(() => {
-        return document.querySelector('div.compose').childNodes.length;
-    });
-    await t.expect(await getChildNodeCount()).eql(2);
-
-    const firstEmoji = await Selector('div.compose img').nth(0)();
-    await t.expect(firstEmoji.tagName).eql('img');
-    await t.expect(firstEmoji.attributes.title).eql(':woman_zombie:');
-    await t.expect(firstEmoji.classNames).eql(['e1']);
-
-    const secondEmoji = await Selector('div.compose img').nth(1)();
-    await t.expect(secondEmoji.tagName).eql('img');
-    await t.expect(secondEmoji.attributes.title).eql(':beers:');
-    await t.expect(secondEmoji.classNames).eql(['e1']);
-});
+    await driver.findElement(By.className('e1-food')).click();
+    await driver.findElement(By.css('.e1._1f37b')).click();
+
+    // Validate emoji
+    const emoji = await driver.findElement(composeArea).findElements(By.xpath('*'));
+    expect(emoji.length).to.equal(2);
+    expect(await emoji[0].getAttribute('title')).to.equal(':woman_zombie:');
+    expect(await emoji[1].getAttribute('title')).to.equal(':beers:');
+
+    // Validate text
+    const html = await driver.findElement(composeArea).getAttribute('innerHTML');
+    expect(/>hi<img/.test(html)).to.be.true;
+}
+
+/**
+ * Insert a newline using shift-enter.
+ */
+async function insertNewline(driver: WebDriver) {
+    // Insert text
+    await driver.findElement(composeArea).click();
+    await driver.findElement(composeArea).sendKeys('hello');
+    await driver.findElement(composeArea).sendKeys(Key.SHIFT, Key.ENTER);
+    await driver.findElement(composeArea).sendKeys('threema');
+    await driver.findElement(composeArea).sendKeys(Key.SHIFT, Key.ENTER);
+    await driver.findElement(composeArea).sendKeys('web');
+
+    const script = `
+        ${extractText.toString()}
+        const element = document.querySelector("div.compose");
+        return extractText(element);
+    `;
+    const text = await driver.executeScript<string>(script);
+    expect(text).to.equal('hello\nthreema\nweb');
+}
+
+// Register tests here
+const TESTS: Array<[string, Testfunc]> = [
+    ['Show and hide emoji selector', showEmojiSelector],
+    ['Insert emoji and text', insertEmoji],
+    ['Insert three lines of text', insertNewline],
+];
+
+// Test runner
+const TEST_URL = 'http://localhost:7777/tests/ui/compose_area.html';
+(async function() {
+    const driver: WebDriver = await new Builder().forBrowser(browser).build();
+    let i = 0;
+    let success = 0;
+    let failed = 0;
+    console.info('\n====== THREEMA WEB UI TESTS ======\n');
+    try {
+        for (const [name, testfunc] of TESTS) {
+            console.info(TermColor.blue(`» ${i + 1}: Running test: ${name}`));
+            await driver.get(TEST_URL);
+            await testfunc(driver);
+            success++;
+            i++;
+        }
+    } catch (e) {
+        console.error(TermColor.red(`\nTest failed:`));
+        console.error(e);
+        failed++;
+    } finally {
+        await driver.quit();
+    }
+    const colorFunc = failed > 0 ? TermColor.red : TermColor.green;
+    console.info(colorFunc(`\nSummary: ${i} tests run, ${success} succeeded, ${failed} failed`));
+    process.exit(failed > 0 ? 1 : 0);
+})();

Một số tệp đã không được hiển thị bởi vì quá nhiều tập tin thay đổi trong này khác