diff --git a/.babelrc b/.babelrc index 97242d46ff06d0c89e2652bd3bd629e722def056..0cb0bd16047bb69cd9786166ba7709906320358e 100644 --- a/.babelrc +++ b/.babelrc @@ -1,6 +1,7 @@ { "plugins": [ "syntax-dynamic-import", + "transform-async-to-generator", "transform-object-rest-spread", ["react-intl", { "messagesDir": "./translations/messages/" diff --git a/.travis.yml b/.travis.yml index cc8a5822a40f0588d27d8e81a5c613b940fcde2d..ec6ccee6c4196c66b195391b41637c38633ca256 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,6 +1,11 @@ language: node_js sudo: false dist: trusty +addons: + chrome: stable +before_script: + - "export DISPLAY=:99.0" + - "sh -e /etc/init.d/xvfb start" node_js: - 6 cache: diff --git a/README.md b/README.md index b478b1e47504962bd77f2cc115da6e57c2dabea1..c036a170930285319aff1fe3f5b348883df9ec65 100644 --- a/README.md +++ b/README.md @@ -31,7 +31,7 @@ Then go to [http://localhost:8601/](http://localhost:8601/) - the playground out ## Testing NOTE: If you're a windows user, please run these scripts in Windows `cmd.exe` instead of Git Bash/MINGW64. -Run linter, unit tests, and build. +Run linter, unit tests, build, and integration tests. ```bash npm test ``` @@ -46,6 +46,11 @@ Run unit tests in watch mode (watches for code changes and continuously runs tes npm run unit-test -- --watch ``` +Run integration tests in isolation. +```bash +npm run integration-test +``` + You may want to review the documentation for [Jest](https://facebook.github.io/jest/docs/en/api.html) and [Enzyme](http://airbnb.io/enzyme/docs/api/) as you write your tests. ## Publishing to GitHub Pages diff --git a/package.json b/package.json index e51c1bcbc18109ae0d65066220e0d80d7e75d8c7..bad718d7eecc7b33968243a26795c72d09fe0029 100644 --- a/package.json +++ b/package.json @@ -11,8 +11,9 @@ "i18n:src": "babel src > tmp.js && rimraf tmp.js && ./scripts/build-i18n-source.js ./translations/messages/ ./translations/", "lint": "eslint . --ext .js,.jsx", "start": "npm run i18n:msgs && webpack-dev-server", - "unit-test": "jest", - "test": "npm run lint && npm run unit-test && npm run build", + "unit-test": "jest test/unit", + "integration-test": "npm run build && jest test/integration", + "test": "npm run lint && npm run unit-test && npm run integration-test", "watch": "webpack --progress --colors --watch" }, "author": "Massachusetts Institute of Technology", @@ -34,9 +35,11 @@ "babel-loader": "^7.0.0", "babel-plugin-react-intl": "2.3.1", "babel-plugin-syntax-dynamic-import": "6.18.0", + "babel-plugin-transform-async-to-generator": "^6.24.1", "babel-plugin-transform-object-rest-spread": "^6.22.0", "babel-preset-es2015": "^6.22.0", "babel-preset-react": "^6.22.0", + "chromedriver": "^2.31.0", "classnames": "2.2.5", "copy-webpack-plugin": "4.0.1", "css-loader": "0.28.3", @@ -83,6 +86,7 @@ "scratch-render": "latest", "scratch-storage": "^0.2.0", "scratch-vm": "latest", + "selenium-webdriver": "^3.5.0", "style-loader": "^0.18.0", "svg-to-image": "1.1.3", "svg-url-loader": "2.1.0", diff --git a/src/lib/libraries/costumes.json b/src/lib/libraries/costumes.json index 370f2d8b95b60b31f319982dd5bf547f6e1b4595..f359a3f16670dd17f324c32b6e4585d2d6395678 100644 --- a/src/lib/libraries/costumes.json +++ b/src/lib/libraries/costumes.json @@ -7717,7 +7717,7 @@ 109, 32 ], - "md5": "4cddf35440090d30d4de188625d609c9.svg", + "md5": "0a54c19714962c197532f68c56fde123.svg", "type": "costume", "name": "text awesome", "tags": [ @@ -7731,7 +7731,7 @@ 135, 24 ], - "md5": "6f54c5de1985d7f23a24341e1688c4b0.svg", + "md5": "97c9e6ef78d4f1987fd8c6e5042963cb.svg", "type": "costume", "name": "text keep scratching", "tags": [ @@ -7745,7 +7745,7 @@ 172, 30 ], - "md5": "e634628e4b948573710f7e4a4f50659b.svg", + "md5": "9fde2e190a9d01c08737dff291bdd4a8.svg", "type": "costume", "name": "text valentine's day", "tags": [ @@ -7759,7 +7759,7 @@ 165, 34 ], - "md5": "7ebe62c6f2b7c8b644d2abe036b8f69e.svg", + "md5": "0176a402a133f7b833da61b890e0d73e.svg", "type": "costume", "name": "text Halloween", "tags": [ @@ -10629,4 +10629,4 @@ "vector" ] } -] \ No newline at end of file +] diff --git a/test/helpers/selenium-helpers.js b/test/helpers/selenium-helpers.js new file mode 100644 index 0000000000000000000000000000000000000000..5ee0af01e65a379c5dee5f92f26536e3aa98dab4 --- /dev/null +++ b/test/helpers/selenium-helpers.js @@ -0,0 +1,58 @@ +/* eslint-env jest */ +jasmine.DEFAULT_TIMEOUT_INTERVAL = 30000; // eslint-disable-line no-undef + +import webdriver from 'selenium-webdriver'; + +const {By, until} = webdriver; + +const driver = new webdriver.Builder() + .forBrowser('chrome') + .build(); + +const findByXpath = (xpath) => { + return driver.wait(until.elementLocated(By.xpath(xpath), 5 * 1000)); +}; + +const clickXpath = (xpath) => { + return findByXpath(xpath).then(el => el.click()); +}; + +const clickText = (text) => { + return clickXpath(`//*[contains(text(), '${text}')]`); +}; + +const clickButton = (text) => { + return clickXpath(`//button[contains(text(), '${text}')]`); +}; + +const getLogs = (whitelist) => { + return driver.manage() + .logs() + .get('browser') + .then((entries) => { + return entries.filter((entry) => { + const message = entry.message; + for (let i = 0; i < whitelist.length; i++) { + if (message.indexOf(whitelist[i]) !== -1) { + // eslint-disable-next-line no-console + console.warn('Ignoring whitelisted error: ' + whitelist[i]); + return false; + } else if (entry.level !== 'SEVERE') { + // eslint-disable-next-line no-console + console.warn('Ignoring non-SEVERE entry: ' + message); + return false; + } + return true; + } + }); + }); +}; + +export { + clickText, + clickButton, + clickXpath, + driver, + findByXpath, + getLogs +}; diff --git a/test/integration/test.js b/test/integration/test.js new file mode 100644 index 0000000000000000000000000000000000000000..7e3e60a651a6e7d64f06ad56c561058449432f20 --- /dev/null +++ b/test/integration/test.js @@ -0,0 +1,89 @@ +/* eslint-env jest */ +/* globals Promise */ + +import path from 'path'; +import { + clickText, + clickButton, + clickXpath, + driver, + findByXpath, + getLogs +} from '../helpers/selenium-helpers'; + +const uri = path.resolve(__dirname, '../../build/index.html'); + +const errorWhitelist = [ + 'The play() request was interrupted by a call to pause()' +]; + +describe('costumes, sounds and variables', () => { + afterAll(async () => { + await driver.quit(); + }); + + test('Adding a costume', async () => { + await driver.get('file://' + uri); + await clickText('Costumes'); + await clickText('Add Costume'); + const el = await findByXpath("//input[@placeholder='what are you looking for?']"); + await el.sendKeys('abb'); + await clickText('abby-a'); // Should close the modal, then click the costumes in the selector + await clickText('costume1'); + await clickText('abby-a'); + const logs = await getLogs(errorWhitelist); + await expect(logs).toEqual([]); + }); + + test('Adding a sound', async () => { + await driver.get('file://' + uri); + await clickText('Sounds'); + await clickText('Add Sound'); + const el = await findByXpath("//input[@placeholder='what are you looking for?']"); + await el.sendKeys('chom'); + await clickText('chomp'); // Should close the modal, then click the sounds in the selector + await clickText('meow'); + await clickText('chomp'); + await clickXpath('//button[@title="Play"]'); + await clickText('meow'); + await clickXpath('//button[@title="Play"]'); + + await clickText('Louder'); + await clickText('Softer'); + await clickText('Faster'); + await clickText('Slower'); + await clickText('Robot'); + await clickText('Echo'); + await clickText('Reverse'); + + const logs = await getLogs(errorWhitelist); + await expect(logs).toEqual([]); + }); + + test('Load a project by ID', async () => { + const projectId = '96708228'; + await driver.get('file://' + uri + '#' + projectId); + await new Promise(resolve => setTimeout(resolve, 2000)); + await clickXpath('//img[@title="Go"]'); + await new Promise(resolve => setTimeout(resolve, 2000)); + await clickXpath('//img[@title="Stop"]'); + const logs = await getLogs(errorWhitelist); + await expect(logs).toEqual([]); + }); + + test('Creating variables', async () => { + await driver.get('file://' + uri); + await clickText('Blocks'); + await clickText('Data'); + await clickText('Create variable...'); + let el = await findByXpath("//input[@placeholder='']"); + await el.sendKeys('score'); + await clickButton('OK'); + await clickText('Create variable...'); + el = await findByXpath("//input[@placeholder='']"); + await el.sendKeys('second variable'); + await clickButton('OK'); + const logs = await getLogs(errorWhitelist); + await expect(logs).toEqual([]); + }); +});