diff --git a/.circleci/config.yml b/.circleci/config.yml new file mode 100644 index 000000000..d280fda2c --- /dev/null +++ b/.circleci/config.yml @@ -0,0 +1,277 @@ +# job_defaults applies all the defaults for each job. +job_defaults: &job_defaults + working_directory: ~/coralproject/talk + docker: + - image: circleci/node:8 + +# integration_environment is the environment that configures the tests. +integration_environment: &integration_environment + NODE_ENV: test + CIRCLE_TEST_REPORTS: /tmp/circleci-test-results + +# integration_job runs the integration tests and saves the test results. +integration_job: &integration_job + <<: *job_defaults + environment: + <<: *integration_environment + docker: + - image: circleci/node:8-browsers + - image: circleci/mongo:3 + - image: circleci/redis:4-alpine + steps: + - checkout + - attach_workspace: + at: ~/coralproject/talk + - run: + name: Setup the database with defaults + command: ./bin/cli setup --defaults + - run: + name: Run the integration tests + command: bash .circleci/e2e.sh + - store_test_results: + path: /tmp/circleci-test-results + +version: 2 +jobs: + # npm_dependencies will install the dependencies used by all other steps. + npm_dependencies: + <<: *job_defaults + steps: + - checkout + - attach_workspace: + at: ~/coralproject/talk + - restore_cache: + key: dependency-cache-{{ checksum "yarn.lock" }} + - run: + name: Install dependencies + command: | + yarn global add node-gyp && + yarn install --frozen-lockfile + - save_cache: + key: dependency-cache-{{ checksum "yarn.lock" }} + paths: + - ./node_modules + - persist_to_workspace: + root: . + paths: node_modules + + # lint will perform file linting. + lint: + <<: *job_defaults + steps: + - checkout + - attach_workspace: + at: ~/coralproject/talk + - run: + name: Perform linting + command: yarn lint + + # build_assets will build the static assets. + build_assets: + <<: *job_defaults + steps: + - checkout + - attach_workspace: + at: ~/coralproject/talk + - restore_cache: + keys: + - build-cache-{{ .Branch }}-{{ .Revision }} + - build-cache-{{ .Branch }}- + - build-cache- + - run: + name: Build static assets + command: yarn build + - save_cache: + key: build-cache-{{ .Branch }}-{{ .Revision }} + paths: + - ./node_modules/.cache/hard-source + - persist_to_workspace: + root: . + paths: dist + + # test_unit will run the unit tests. + test_unit: + <<: *job_defaults + docker: + - image: circleci/node:8 + - image: circleci/mongo:3 + - image: circleci/redis:4-alpine + steps: + - checkout + - attach_workspace: + at: ~/coralproject/talk + - run: + name: Setup the test results directory + command: mkdir -p /tmp/circleci-test-results + - run: + name: Run the unit tests + command: yarn test + environment: + MOCHA_FILE: /tmp/circleci-test-results/junit/test-results.xml + MOCHA_REPORTER: mocha-junit-reporter + NODE_ENV: test + - store_test_results: + path: /tmp/circleci-test-results + + # test_integration_chrome_local will run the integration tests locally with + # chrome headless. + test_integration_chrome_local: + <<: *integration_job + environment: + <<: *integration_environment + E2E_BROWSERS: chrome + + # test_integration_firefox_local will run the integration tests locally with + # firefox headless. + test_integration_firefox_local: + <<: *integration_job + environment: + <<: *integration_environment + E2E_BROWSERS: firefox + + # test_integration_chrome will run the integration tests with chrome in + # browserstack. + test_integration_chrome: + <<: *integration_job + environment: + <<: *integration_environment + BROWSERSTACK: true + E2E_BROWSERS: chrome + + # test_integration_firefox will run the integration tests with firefox in + # browserstack. + test_integration_firefox: + <<: *integration_job + environment: + <<: *integration_environment + BROWSERSTACK: true + E2E_BROWSERS: firefox + + # test_integration_edge will run the integration tests with edge in + # browserstack. + test_integration_edge: + <<: *integration_job + environment: + <<: *integration_environment + BROWSERSTACK: true + E2E_BROWSERS: edge + + # test_integration_ie will run the integration tests with ie in + # browserstack. + test_integration_ie: + <<: *integration_job + environment: + <<: *integration_environment + BROWSERSTACK: true + E2E_BROWSERS: ie + + # test_integration_safari will run the integration tests with safari in + # browserstack. + test_integration_safari: + <<: *integration_job + environment: + <<: *integration_environment + BROWSERSTACK: true + E2E_BROWSERS: safari + + # deploy will deploy the application as a docker image. + deploy: + <<: *job_defaults + steps: + - checkout + - setup_remote_docker + - run: + name: Deploy the code + command: bash ./scripts/docker.sh + +# filter_deploy will add the filters for a deploy job in a workflow to make it +# only execute on a deploy related job. +filter_deploy: &filter_deploy + filters: + branches: + only: + - master + - next + tags: + only: /v[0-9]+(\.[0-9]+)*/ + +# filter_develop will add the filters for a development related commit. +filter_develop: &filter_develop + filters: + branches: + ignore: + - master + - next + +workflows: + version: 2 + + # All PR's will hit this workflow. + build-and-test: + jobs: + - npm_dependencies: + <<: *filter_develop + - lint: + <<: *filter_develop + requires: + - npm_dependencies + - test_unit: + <<: *filter_develop + requires: + - npm_dependencies + - build_assets: + <<: *filter_develop + requires: + - npm_dependencies + - test_integration_chrome_local: + <<: *filter_develop + requires: + - build_assets + - test_integration_firefox_local: + <<: *filter_develop + requires: + - build_assets + deploy-tagged: + jobs: + - npm_dependencies: + <<: *filter_deploy + - lint: + <<: *filter_deploy + requires: + - npm_dependencies + - test_unit: + <<: *filter_deploy + requires: + - npm_dependencies + - build_assets: + <<: *filter_deploy + requires: + - npm_dependencies + - test_integration_chrome: + <<: *filter_deploy + requires: + - build_assets + - test_integration_firefox: + <<: *filter_deploy + requires: + - build_assets + - test_integration_edge: + <<: *filter_deploy + requires: + - build_assets + - test_integration_ie: + <<: *filter_deploy + requires: + - build_assets + - test_integration_safari: + <<: *filter_deploy + requires: + - build_assets + - deploy: + <<: *filter_deploy + requires: + - lint + - test_unit + - test_integration_chrome + - test_integration_firefox + - test_integration_edge \ No newline at end of file diff --git a/scripts/e2e-ci.sh b/.circleci/e2e.sh similarity index 65% rename from scripts/e2e-ci.sh rename to .circleci/e2e.sh index a509ccee7..2d9fc25af 100755 --- a/scripts/e2e-ci.sh +++ b/.circleci/e2e.sh @@ -19,11 +19,11 @@ if [[ "${E2E_DISABLE}" == "true" ]]; then exit fi -if [[ "${CIRCLE_BRANCH}" == "master" && -n "$BROWSERSTACK_KEY" ]]; then +if [[ "$BROWSERSTACK" == "true" && -n "$BROWSERSTACK_KEY" ]]; then echo Testing on browserstack - yarn e2e --reports-folder "$REPORTS_FOLDER" --bs-key "$BROWSERSTACK_KEY" --retries "$E2E_MAX_RETRIES" --timeout "$E2E_WAIT_FOR_TIMEOUT" --browsers "$E2E_BROWSERS" + node scripts/e2e.js --reports-folder "$REPORTS_FOLDER" --retries "$E2E_MAX_RETRIES" --timeout "$E2E_WAIT_FOR_TIMEOUT" --browsers "$E2E_BROWSERS" --browserstack else # When browserstack is not available test locally using chrome headless. echo Testing locally - yarn e2e --reports-folder "$REPORTS_FOLDER" --retries "$E2E_MAX_RETRIES" --headless + node scripts/e2e.js --reports-folder "$REPORTS_FOLDER" --retries "$E2E_MAX_RETRIES" --timeout "$E2E_WAIT_FOR_TIMEOUT" --browsers "$E2E_BROWSERS" --headless fi diff --git a/circle.yml b/circle.yml deleted file mode 100644 index bd5c2c520..000000000 --- a/circle.yml +++ /dev/null @@ -1,75 +0,0 @@ -machine: - node: - version: 8 - services: - - docker - - redis - environment: - PATH: "${PATH}:${HOME}/${CIRCLE_PROJECT_REPONAME}/node_modules/.bin" - NODE_ENV: "test" - MOCHA_FILE: "${CIRCLE_TEST_REPORTS}/junit/test-results.xml" - MOCHA_REPORTER: "mocha-junit-reporter" - pre: - # TODO: use the following to add in support for MongoDB 3.4. - # # Upgrade the database version to 3.4. - # - sudo apt-get purge mongodb-org* - # - sudo apt-key adv --keyserver hkp://keyserver.ubuntu.com:80 --recv 0C49F3730359A14518585931BC711F9BA15703C6 - # - echo "deb [ arch=amd64 ] http://repo.mongodb.org/apt/ubuntu precise/mongodb-org/3.4 multiverse" | sudo tee /etc/apt/sources.list.d/mongodb-org-3.4.list - # - sudo apt-get update - # - sudo apt-get install -y mongodb-org - # - sudo service mongod restart - - # Force sync the time. - - date - - sudo service ntp stop - - sudo ntpdate -s time.nist.gov - - sudo service ntp start - - date - - # Install chromium for e2e and remove old google-chrome - - sudo rm -rf /opt/google/chrome - - sudo rm -f /usr/bin/google-chrome* - - sudo apt-get update - - sudo apt-get install chromium-browser - -dependencies: - override: - - # Install node dependencies. - - yarn --version - - yarn global add node-gyp --force - - yarn - - post: - # Build the static assets. - - yarn build - # Lint the project here, before tests are ran. - - yarn lint - -database: - post: - # Initialize the settings in the database, this will create indicies for the - # database. - - ./bin/cli setup --defaults - - sleep 2 - -test: - override: - # Run the tests using the junit reporter. - - yarn test - # Run the end to end tests - - yarn e2e:ci - -deployment: - release: - tag: /v[0-9]+(\.[0-9]+)*/ - commands: - - bash ./scripts/docker.sh deploy - - latest: - branch: - - master - - next - owner: coralproject - commands: - - bash ./scripts/docker.sh deploy diff --git a/nightwatch.conf.js b/nightwatch.conf.js index ac5887e51..76a5c291b 100644 --- a/nightwatch.conf.js +++ b/nightwatch.conf.js @@ -49,5 +49,12 @@ module.exports = { }, }, }, + 'firefox-headless': { + desiredCapabilities: { + 'moz:firefoxOptions': { + args: ['-headless'], + }, + }, + }, }, }; diff --git a/package.json b/package.json index 48e0b1001..9c5f58ff5 100644 --- a/package.json +++ b/package.json @@ -115,6 +115,7 @@ "graphql-tag": "^1.2.3", "graphql-tools": "^0.10.1", "hammerjs": "^2.0.8", + "hard-source-webpack-plugin": "^0.6.0", "helmet": "3.8.2", "history": "^3.0.0", "hjson": "^3.1.1", diff --git a/scripts/e2e.js b/scripts/e2e.js index fb010f9e5..37ada228a 100755 --- a/scripts/e2e.js +++ b/scripts/e2e.js @@ -139,61 +139,81 @@ async function runBrowserTests( return Object.keys(succeeded).length === browsers.length; } -async function start(program) { - const localIdentifier = uuid(); - let browsers = program.browsers.split(','); - const retries = Number.parseInt(program.retries); - const browserstack = {}; - const date = new Date() - .toISOString() - .replace(/[T.]/g, '-') - .replace(/:/g, ''); - const timeout = program.timeout; - const reportsFolder = `${program.reportsFolder}/${date}`; - let exitCode = 0; - let config = 'nightwatch.conf.js'; - try { - if (program.tunnel && program.bsKey) { - await startTunnel(program.bsKey, localIdentifier); +class E2E { + constructor(opts) { + this.browserstackEnabled = Boolean(opts.bsKey && opts.browserstack); + if (this.browserstackEnabled && opts.headless) { + console.error('--browserstack and --headless cannot be combined'); + process.exit(1); } - console.log('Dropping test database'); - await mongoose.connection.dropDatabase(); - await serve(); - - if (program.bsKey) { - config = 'nightwatch-browserstack.conf.js'; - browserstack.localIdentifier = localIdentifier; - browserstack.key = program.bsKey; - browserstack.user = program.bsUser; - } else { - // Install selenium standalone. - await seleniumInstall(); - if (program.headless) { - browsers = browsers.map(b => `${b}-headless`); + this.localIdentifier = uuid(); + this.retries = Number.parseInt(opts.retries); + this.browserstack = {}; + this.date = new Date() + .toISOString() + .replace(/[T.]/g, '-') + .replace(/:/g, ''); + this.browsers = opts.browsers.split(',').map(browser => { + if (opts.headless) { + return `${browser}-headless`; } - } - const succeeded = await runBrowserTests( - browsers, - config, - retries, - reportsFolder, - browserstack, - timeout - ); - if (!succeeded) { - exitCode = 1; - } - } catch (ex) { - console.log('There was an error:\n\n'); - process.stderr.write(`${ex.stack}\n`); - process.exit(2); - } finally { - console.log('Shutting down'); - shutdown(); + return browser; + }); + this.timeout = opts.timeout; + this.reportsFolder = `${opts.reportsFolder}/${this.date}`; + this.config = this.browserstackEnabled + ? 'nightwatch-browserstack.conf.js' + : 'nightwatch.conf.js'; + this.tunnel = opts.tunnel; + this.bsKey = opts.bsKey; + this.bsUser = opts.bsUser; + } + + async start() { + let exitCode = 0; + try { + if (this.tunnel && this.bsKey) { + await startTunnel(this.bsKey, this.localIdentifier); + } + + console.log('Dropping test database'); + await mongoose.connection.dropDatabase(); + console.log('Starting the server'); + await serve(); + + if (this.browserstackEnabled) { + browserstack.localIdentifier = this.localIdentifier; + browserstack.key = this.bsKey; + browserstack.user = this.bsUser; + } else { + // Install selenium standalone. + await seleniumInstall(); + } + + // Run the tests. + const succeeded = await runBrowserTests( + this.browsers, + this.config, + this.retries, + this.reportsFolder, + browserstack, + this.timeout + ); + if (!succeeded) { + exitCode = 1; + } + } catch (err) { + console.log('There was an error:\n\n'); + process.stderr.write(`${err.stack}\n`); + process.exit(2); + } finally { + console.log('Shutting down'); + shutdown(); + process.exit(exitCode); + } } - process.exit(exitCode); } program @@ -202,7 +222,12 @@ program 'Perform e2e testing locally or if browserstack credentials are provided on browserstack.' ) .option('-u, --bs-user [user]', 'Browserstack user', 'coralproject2') - .option('-k, --bs-key [key]', 'Browserstack api key') + .option( + '-k, --bs-key [key]', + 'Browserstack api key', + process.env.BROWSERSTACK_KEY + ) + .option('--browserstack', 'Enables Browserstack testing') .option('--no-tunnel', 'Dont start browserstack-local') .option('-b, --browsers [list of browsers]', 'Browsers to test', 'chrome') .option('-r, --retries [number]', 'Number of retries before failing', '1') @@ -211,4 +236,5 @@ program .option('--timeout [number]', 'Timeout for WaitForConditions', '10000') .parse(process.argv); -start(program); +const e2e = new E2E(program); +e2e.start(); diff --git a/webpack.config.js b/webpack.config.js index 00daeedee..2027061da 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -8,6 +8,7 @@ const _ = require('lodash'); const Copy = require('copy-webpack-plugin'); const webpack = require('webpack'); const UglifyJsPlugin = require('uglifyjs-webpack-plugin'); +const HardSourceWebpackPlugin = require('hard-source-webpack-plugin'); const debug = require('debug')('talk:webpack'); // Possibly load the config from the .env file (if there is one). @@ -136,6 +137,7 @@ const config = { TALK_DEFAULT_LANG: 'en', }), new webpack.IgnorePlugin(/^\.\/locale$/, /moment$/), + new HardSourceWebpackPlugin(), ], resolveLoader: { modules: [ diff --git a/yarn.lock b/yarn.lock index 7ba8aab3f..62eb054fb 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2683,6 +2683,10 @@ detect-indent@^4.0.0: dependencies: repeating "^2.0.0" +detect-indent@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/detect-indent/-/detect-indent-5.0.0.tgz#3871cc0a6a002e8c3e5b3cf7f336264675f06b9d" + detect-libc@^0.2.0: version "0.2.0" resolved "https://registry.yarnpkg.com/detect-libc/-/detect-libc-0.2.0.tgz#47fdf567348a17ec25fcbf0b9e446348a76f9fb5" @@ -4237,6 +4241,19 @@ har-validator@~5.0.3: ajv "^5.1.0" har-schema "^2.0.0" +hard-source-webpack-plugin@^0.6.0: + version "0.6.0" + resolved "https://registry.yarnpkg.com/hard-source-webpack-plugin/-/hard-source-webpack-plugin-0.6.0.tgz#ec2f60068a8d1f358439b7b1587f1c64fe642eda" + dependencies: + lodash "^4.15.0" + mkdirp "^0.5.1" + node-object-hash "^1.2.0" + rimraf "^2.6.2" + tapable "^1.0.0-beta.5" + webpack-core "~0.6.0" + webpack-sources "^1.0.1" + write-json-file "^2.3.0" + harmony-reflect@^1.4.6: version "1.5.1" resolved "https://registry.yarnpkg.com/harmony-reflect/-/harmony-reflect-1.5.1.tgz#b54ca617b00cc8aef559bbb17b3d85431dc7e329" @@ -7043,6 +7060,10 @@ node-notifier@^5.0.2: shellwords "^0.1.0" which "^1.2.12" +node-object-hash@^1.2.0: + version "1.3.0" + resolved "https://registry.yarnpkg.com/node-object-hash/-/node-object-hash-1.3.0.tgz#7f294f5afec6b08d713e40d40a95ec793e05baf3" + node-pre-gyp@^0.6.36, node-pre-gyp@^0.6.39: version "0.6.39" resolved "https://registry.yarnpkg.com/node-pre-gyp/-/node-pre-gyp-0.6.39.tgz#c00e96860b23c0e1420ac7befc5044e1d78d8649" @@ -9222,7 +9243,7 @@ right-align@^0.1.1: dependencies: align-text "^0.1.1" -rimraf@2, rimraf@^2.5.1, rimraf@^2.5.4, rimraf@^2.6.1: +rimraf@2, rimraf@^2.5.1, rimraf@^2.5.4, rimraf@^2.6.1, rimraf@^2.6.2: version "2.6.2" resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-2.6.2.tgz#2ed8150d24a16ea8651e6d6ef0f47c4158ce7a36" dependencies: @@ -9686,10 +9707,20 @@ sort-keys@^1.0.0: dependencies: is-plain-obj "^1.0.0" +sort-keys@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/sort-keys/-/sort-keys-2.0.0.tgz#658535584861ec97d730d6cf41822e1f56684128" + dependencies: + is-plain-obj "^1.0.0" + source-list-map@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/source-list-map/-/source-list-map-2.0.0.tgz#aaa47403f7b245a92fbc97ea08f250d6087ed085" +source-list-map@~0.1.7: + version "0.1.8" + resolved "https://registry.yarnpkg.com/source-list-map/-/source-list-map-0.1.8.tgz#c550b2ab5427f6b3f21f5afead88c4f5587b2106" + source-map-resolve@^0.5.0: version "0.5.1" resolved "https://registry.yarnpkg.com/source-map-resolve/-/source-map-resolve-0.5.1.tgz#7ad0f593f2281598e854df80f19aae4b92d7a11a" @@ -9722,7 +9753,7 @@ source-map@0.1.x: dependencies: amdefine ">=0.0.4" -source-map@0.4.x, source-map@^0.4.4: +source-map@0.4.x, source-map@^0.4.4, source-map@~0.4.1: version "0.4.4" resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.4.4.tgz#eba4f5da9c0dc999de68032d8b4f76173652036b" dependencies: @@ -10100,6 +10131,10 @@ tapable@^0.2.7: version "0.2.8" resolved "https://registry.yarnpkg.com/tapable/-/tapable-0.2.8.tgz#99372a5c999bf2df160afc0d74bed4f47948cd22" +tapable@^1.0.0-beta.5: + version "1.0.0" + resolved "https://registry.yarnpkg.com/tapable/-/tapable-1.0.0.tgz#cbb639d9002eed9c6b5975eb20598d7936f1f9f2" + tar-fs@^1.13.0: version "1.16.0" resolved "https://registry.yarnpkg.com/tar-fs/-/tar-fs-1.16.0.tgz#e877a25acbcc51d8c790da1c57c9cf439817b896" @@ -10717,6 +10752,13 @@ webidl-conversions@^4.0.0, webidl-conversions@^4.0.1, webidl-conversions@^4.0.2: version "4.0.2" resolved "https://registry.yarnpkg.com/webidl-conversions/-/webidl-conversions-4.0.2.tgz#a855980b1f0b6b359ba1d5d9fb39ae941faa63ad" +webpack-core@~0.6.0: + version "0.6.9" + resolved "https://registry.yarnpkg.com/webpack-core/-/webpack-core-0.6.9.tgz#fc571588c8558da77be9efb6debdc5a3b172bdc2" + dependencies: + source-list-map "~0.1.7" + source-map "~0.4.1" + webpack-sources@^1.0.1, webpack-sources@^1.0.2, webpack-sources@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/webpack-sources/-/webpack-sources-1.1.0.tgz#a101ebae59d6507354d71d8013950a3a8b7a5a54" @@ -10897,6 +10939,17 @@ write-file-atomic@^2.0.0, write-file-atomic@^2.1.0: imurmurhash "^0.1.4" signal-exit "^3.0.2" +write-json-file@^2.3.0: + version "2.3.0" + resolved "https://registry.yarnpkg.com/write-json-file/-/write-json-file-2.3.0.tgz#2b64c8a33004d54b8698c76d585a77ceb61da32f" + dependencies: + detect-indent "^5.0.0" + graceful-fs "^4.1.2" + make-dir "^1.0.0" + pify "^3.0.0" + sort-keys "^2.0.0" + write-file-atomic "^2.0.0" + write@^0.2.1: version "0.2.1" resolved "https://registry.yarnpkg.com/write/-/write-0.2.1.tgz#5fc03828e264cea3fe91455476f7a3c566cb0757"