diff --git a/.github/workflows/frontend-collab-tests.yml b/.github/workflows/frontend-collab-tests.yml new file mode 100644 index 000000000..ac801740c --- /dev/null +++ b/.github/workflows/frontend-collab-tests.yml @@ -0,0 +1,117 @@ +# Leave the powered by Sauce Labs bit in as this means we get additional concurrency +name: "Frontend tests powered by Sauce Labs" + +on: [push] + +jobs: + withoutplugins: + name: without plugins + runs-on: ubuntu-latest + + steps: + - name: Generate Sauce Labs strings + id: sauce_strings + run: | + printf %s\\n '::set-output name=name::${{ github.workflow }} - ${{ github.job }}' + printf %s\\n '::set-output name=tunnel_id::${{ github.run_id }}-${{ github.run_number }}-${{ github.job }}' + + - name: Checkout repository + uses: actions/checkout@v2 + + - uses: actions/setup-node@v2 + with: + node-version: 12 + + - name: Install all dependencies and symlink for ep_etherpad-lite + run: src/bin/installDeps.sh + + - name: export GIT_HASH to env + id: environment + run: echo "::set-output name=sha_short::$(git rev-parse --short ${{ github.sha }})" + + - name: Create settings.json + run: cp settings.json.template settings.json + + - name: Disable import/export rate limiting + run: | + sed -e '/^ *"importExportRateLimiting":/,/^ *\}/ s/"max":.*/"max": 0/' -i settings.json + + - uses: saucelabs/sauce-connect-action@v1 + with: + username: ${{ secrets.SAUCE_USERNAME }} + accessKey: ${{ secrets.SAUCE_ACCESS_KEY }} + tunnelIdentifier: ${{ steps.sauce_strings.outputs.tunnel_id }} + + - name: Run the frontend tests + shell: bash + env: + SAUCE_USERNAME: ${{ secrets.SAUCE_USERNAME }} + SAUCE_ACCESS_KEY: ${{ secrets.SAUCE_ACCESS_KEY }} + SAUCE_NAME: ${{ steps.sauce_strings.outputs.name }} + TRAVIS_JOB_NUMBER: ${{ steps.sauce_strings.outputs.tunnel_id }} + GIT_HASH: ${{ steps.environment.outputs.sha_short }} + run: | + src/tests/frontend/travis/runner.sh + + withplugins: + name: with plugins + runs-on: ubuntu-latest + + steps: + - name: Generate Sauce Labs strings + id: sauce_strings + run: | + printf %s\\n '::set-output name=name::${{ github.workflow }} - ${{ github.job }}' + printf %s\\n '::set-output name=tunnel_id::${{ github.run_id }}-${{ github.run_number }}-${{ github.job }}' + + - name: Checkout repository + uses: actions/checkout@v2 + + - uses: actions/setup-node@v2 + with: + node-version: 12 + + - name: Install Etherpad plugins + # The --legacy-peer-deps flag is required to work around a bug in npm v7: + # https://github.com/npm/cli/issues/2199 + run: > + npm install --no-save --legacy-peer-deps + # include any plugins here.. + + # This must be run after installing the plugins, otherwise npm will try to + # hoist common dependencies by removing them from src/node_modules and + # installing them in the top-level node_modules. As of v6.14.10, npm's hoist + # logic appears to be buggy, because it sometimes removes dependencies from + # src/node_modules but fails to add them to the top-level node_modules. Even + # if npm correctly hoists the dependencies, the hoisting seems to confuse + # tools such as `npm outdated`, `npm update`, and some ESLint rules. + - name: Install all dependencies and symlink for ep_etherpad-lite + run: src/bin/installDeps.sh + + - name: export GIT_HASH to env + id: environment + run: echo "::set-output name=sha_short::$(git rev-parse --short ${{ github.sha }})" + + - name: Create settings.json + run: cp settings.json.template settings.json + + - name: Disable import/export rate limiting + run: | + sed -e '/^ *"importExportRateLimiting":/,/^ *\}/ s/"max":.*/"max": 0/' -i settings.json + + - uses: saucelabs/sauce-connect-action@v1 + with: + username: ${{ secrets.SAUCE_USERNAME }} + accessKey: ${{ secrets.SAUCE_ACCESS_KEY }} + tunnelIdentifier: ${{ steps.sauce_strings.outputs.tunnel_id }} + + - name: Run the frontend tests + shell: bash + env: + SAUCE_USERNAME: ${{ secrets.SAUCE_USERNAME }} + SAUCE_ACCESS_KEY: ${{ secrets.SAUCE_ACCESS_KEY }} + SAUCE_NAME: ${{ steps.sauce_strings.outputs.name }} + TRAVIS_JOB_NUMBER: ${{ steps.sauce_strings.outputs.tunnel_id }} + GIT_HASH: ${{ steps.environment.outputs.sha_short }} + run: | + src/tests/frontend/travis/collabrunner.sh diff --git a/src/tests/frontend/specs/responsiveness.js b/src/tests/frontend/specs/responsiveness.js index ec63faa10..da137ccbc 100644 --- a/src/tests/frontend/specs/responsiveness.js +++ b/src/tests/frontend/specs/responsiveness.js @@ -1,91 +1,32 @@ 'use strict'; // Test for https://github.com/ether/etherpad-lite/issues/1763 - -// This test fails in Opera, IE and Safari -// Opera fails due to a weird way of handling the order of execution, -// yet actual performance seems fine -// Safari fails due the delay being too great yet the actual performance seems fine -// Firefox might panic that the script is taking too long so will fail -// IE will fail due to running out of memory as it can't fit 2M chars in memory. - -// Just FYI Google Docs crashes on large docs whilst trying to Save, -// it's likely the limitations we are -// experiencing are more to do with browser limitations than improper implementation. -// A ueber fix for this would be to have a separate lower cpu priority -// thread that handles operations that aren't -// visible to the user. - -// Adapted from John McLear's original test case. - -xdescribe('Responsiveness of Editor', function () { +describe('Responsiveness of Editor', function () { // create a new pad before each test run beforeEach(function (cb) { - helper.newPad(cb); + helper.newPad(cb, 'TEST_PAD_collab'); this.timeout(6000); }); - // JM commented out on 8th Sep 2020 for a release, after release this needs uncommenting - // And the test needs to be fixed to work in Firefox 52 on Windows 7. - // I am not sure why it fails on this specific platform - // The errors show this.timeout... then crash the browser but - // I am sure something is actually causing the stack trace and - // I just need to narrow down what, offers to help accepted. - it('Fast response to keypress in pad with large amount of contents', function (done) { - // skip on Windows Firefox 52.0 - if (window.bowser && - window.bowser.windows && window.bowser.firefox && window.bowser.version === '52.0') { - this.skip(); + + it('Fast response to keypress in pad with large amount of contents', async function () { + if (top.window.location.search.indexOf('&collab=true') === -1) this.skip(); + const numberOfEdits = 10; // TODO; edit to 1500 or so + + // wait a minute for everyone to connect + await helper.waitForPromise( + () => parseInt(helper.padChrome$('#online_count').text()) === 4, 60000); + + // send random characters to last div + let i = 0; + while (i < numberOfEdits) { + // Put the text contents into the pad + // intentional white space at end of string + helper.padInner$('div').last().sendkeys(`${Math.random().toString(36).substring(7)} `); + // wait 1500 milliseconds to simulate 40wpm + await wait(1500); + i++; } - const inner$ = helper.padInner$; - const chars = '0000000000'; // row of placeholder chars - const amount = 200000; // number of blocks of chars we will insert - const length = (amount * (chars.length) + 1); // include a counter for each space - let text = ''; // the text we're gonna insert - this.timeout(amount * 150); // Changed from 100 to 150 to allow Mac OSX Safari to be slow. - - // get keys to send - const keyMultiplier = 10; // multiplier * 10 == total number of key events - let keysToSend = ''; - for (let i = 0; i <= keyMultiplier; i++) { - keysToSend += chars; - } - - const textElement = inner$('div'); - textElement.sendkeys('{selectall}'); // select all - textElement.sendkeys('{del}'); // clear the pad text - - for (let i = 0; i <= amount; i++) { - text = `${text + chars} `; // add the chars and space to the text contents - } - inner$('div').first().text(text); // Put the text contents into the pad - - // Wait for the new contents to be on the pad - helper.waitFor(() => inner$('div').text().length > length).done(() => { - // has the text changed? - expect(inner$('div').text().length).to.be.greaterThan(length); - const start = Date.now(); // get the start time - - // send some new text to the screen (ensure all 3 key events are sent) - const el = inner$('div').first(); - for (let i = 0; i < keysToSend.length; ++i) { - const x = keysToSend.charCodeAt(i); - ['keyup', 'keypress', 'keydown'].forEach((type) => { - const e = new $.Event(type); - e.keyCode = x; - el.trigger(e); - }); - } - - helper.waitFor(() => { // Wait for the ability to process - const el = inner$('body'); - if (el[0].textContent.length > amount) return true; - }).done(() => { - const end = Date.now(); // get the current time - const delay = end - start; // get the delay as the current time minus the start time - - expect(delay).to.be.below(600); - done(); - }, 5000); - }, 10000); }); }); + +const wait = (milliseconds) => new Promise((resolve) => setTimeout(resolve, milliseconds)); diff --git a/src/tests/frontend/travis/collabrunner.sh b/src/tests/frontend/travis/collabrunner.sh new file mode 100644 index 000000000..705b926c7 --- /dev/null +++ b/src/tests/frontend/travis/collabrunner.sh @@ -0,0 +1,40 @@ +#!/bin/sh + +pecho() { printf %s\\n "$*"; } +log() { pecho "$@"; } +error() { log "ERROR: $@" >&2; } +fatal() { error "$@"; exit 1; } +try() { "$@" || fatal "'$@' failed"; } + +# Move to the Etherpad base directory. +MY_DIR=$(try cd "${0%/*}" && try pwd -P) || exit 1 +try cd "${MY_DIR}/../../../.." + +log "Assuming src/bin/installDeps.sh has already been run" +node src/node/server.js --experimental-worker "${@}" & +ep_pid=$! + +log "Waiting for Etherpad to accept connections (http://localhost:9001)..." +connected=false +can_connect() { + curl -sSfo /dev/null http://localhost:9001/ || return 1 + connected=true +} +now() { date +%s; } +start=$(now) +while [ $(($(now) - $start)) -le 15 ] && ! can_connect; do + sleep 1 +done +[ "$connected" = true ] \ + || fatal "Timed out waiting for Etherpad to accept connections" +log "Successfully connected to Etherpad on http://localhost:9001" + +# start the remote runner +try cd "${MY_DIR}" +log "Starting the remote runner..." +node remote_runner.js collab +exit_code=$? + +kill "$ep_pid" && wait "$ep_pid" +log "Done." +exit "$exit_code" diff --git a/src/tests/frontend/travis/remote_runner.js b/src/tests/frontend/travis/remote_runner.js index 13498e4e7..f5b411d7d 100644 --- a/src/tests/frontend/travis/remote_runner.js +++ b/src/tests/frontend/travis/remote_runner.js @@ -11,6 +11,7 @@ const config = { }; const isAdminRunner = process.argv[2] === 'admin'; +const isCollabRunner = process.argv[2] === 'collab'; let allTestsPassed = true; // overwrite the default exit code @@ -37,8 +38,14 @@ const sauceTestWorker = async.queue((testSettings, callback) => { // don't know how to print them into output of the tests testSettings.extendedDebugging = true; testSettings.tunnelIdentifier = process.env.TRAVIS_JOB_NUMBER; + let testURL; + if (isCollabRunner) { + testURL = 'http://localhost:9001/tests/frontend/index.html?grep=responsiveness%5C.js&collab=true'; + } else { + testURL = 'http://localhost:9001/tests/frontend/'; + } - browser.init(testSettings).get('http://localhost:9001/tests/frontend/', () => { + browser.init(testSettings).get(testURL, () => { const url = `https://saucelabs.com/jobs/${browser.sessionID}`; console.log(`Remote sauce test '${name}' started! ${url}`);