mirror of
https://github.com/scratchfoundation/scratch-www.git
synced 2024-11-26 09:08:07 -05:00
Merge branch 'Add-Jest' into develop
# Conflicts: # package-lock.json
This commit is contained in:
commit
d3b003c2bb
36 changed files with 3179 additions and 318 deletions
|
@ -129,11 +129,7 @@ jobs:
|
|||
branch:
|
||||
- master
|
||||
- stage: smoke
|
||||
install:
|
||||
- cd test/integration
|
||||
- npm install
|
||||
- cd -
|
||||
script: npm run test:smoke:sauce
|
||||
script: npm run test:integration:remote
|
||||
stages:
|
||||
- test
|
||||
- name: smoke
|
||||
|
|
35
README.md
35
README.md
|
@ -95,10 +95,43 @@ To stop the process that is making the site available to your web browser (creat
|
|||
|
||||
**NOTE:** Because by default `API_HOST=https://api.scratch.mit.edu`, please be aware that, by default, you will be seeing and interacting with real data on the Scratch website.
|
||||
|
||||
### To Test
|
||||
### Unit Tests
|
||||
To run:
|
||||
```bash
|
||||
npm test
|
||||
```
|
||||
This will build the application and run the unit and localization tests. Some of the tests are run using the TAP framework and others run using Jest.
|
||||
|
||||
### Integration tests
|
||||
We are transitioning from using TAP to using Jest as our testing framework so for the time being our tests run using both.
|
||||
|
||||
#### Running the tests
|
||||
|
||||
* By default, tests run against our Staging instance, but you can pass in a different location with the ROOT_URL environment variable (see below) if you want to run the tests against e.g. your local build
|
||||
|
||||
#### Running the tests
|
||||
* Run all tests from the command-line: `$ SMOKE_USERNAME=username SMOKE_PASSWORD=password ROOT_URL=https://scratch.mit.edu npm run test:integration`
|
||||
* To run a single file from the command-line using TAP: `$ SMOKE_USERNAME=username SMOKE_PASSWORD=password ROOT_URL=https://scratch.mit.edu node_modules/.bin/tap ./test/integration-legacy/smoke-testing/filename.js --timeout=3600`
|
||||
* The timeout var is for the length of the entire tap test-suite; if you are getting a timeout error, you may need to adjust this value (some of the Selenium tests take a while to run)
|
||||
* To run a single file from the command-line using Jest: `$ SMOKE_USERNAME=username SMOKE_PASSWORD=password ROOT_URL=https://scratch.mit.edu node_modules/.bin/jest ./test/integration/filename.test.js`
|
||||
|
||||
#### Running Remote tests
|
||||
* TAP tests can be run using Saucelabs, an online service that can test browser/os combinations remotely. Currently all tests are written for use for chrome on mac.
|
||||
* You will need a Saucelabs account in order to use it for testing. To find the Access Key, click your username and select User Settings from the dropdown menu. Near the bottom of the page is your access key that you can copy and use in the command line.
|
||||
* Currently Jest tests will not run with Saucelabs.
|
||||
* To run tests using saucelabs run this command `$ SMOKE_USERNAME=username SMOKE_PASSWORD=password SAUCE_USERNAME=saucelabsUsername SAUCE_ACCESS_KEY=saucelabsAccessKey ROOT_URL=https://scratch.mit.edu npm run test:integration:remote`
|
||||
|
||||
#### Configuration
|
||||
|
||||
| Variable | Default | Description |
|
||||
| --------------------- | --------------------- | --------------------------------------------------------- |
|
||||
| `ROOT_URL` | `scratch.ly` | Location you want to run the tests against |
|
||||
| `SMOKE_USERNAME` | `None` | Username for Scratch user you're signing in with to test |
|
||||
| `SMOKE_PASSWORD` | `None` | Password for Scratch user you're signing in with to test |
|
||||
| `SMOKE_REMOTE` | `false` | Tests with Sauce Labs or not. True if running test:smoke:sauce |
|
||||
| `SMOKE_HEADLESS` | `false` | Run browser in headless mode. Flaky at the moment |
|
||||
| `SAUCE_USERNAME` | `None` | Username for your Sauce Labs account |
|
||||
| `SAUCE_ACCESS_KEY` | `None` | Access Key for Sauce Labs found under User Settings |
|
||||
|
||||
### To Deploy
|
||||
|
||||
|
|
3212
package-lock.json
generated
3212
package-lock.json
generated
File diff suppressed because it is too large
Load diff
20
package.json
20
package.json
|
@ -4,13 +4,18 @@
|
|||
"description": "Standalone WWW client for Scratch",
|
||||
"scripts": {
|
||||
"start": "node ./dev-server/index.js",
|
||||
"test": "npm run test:lint && npm run build && npm run test:tap",
|
||||
"test": "npm run test:lint && npm run build && npm run test:unit",
|
||||
"test:lint": "eslint . --ext .js,.jsx,.json",
|
||||
"test:smoke": "tap ./test/integration/smoke-testing/*.js --timeout=3600 --no-coverage -R classic",
|
||||
"test:smoke:verbose": "tap ./test/integration/smoke-testing/*.js --timeout=3600 --no-coverage -R spec",
|
||||
"test:smoke:sauce": "SMOKE_REMOTE=true tap ./test/integration/smoke-testing/*.js --timeout=60000 --no-coverage -R classic",
|
||||
"test:tap": "tap ./test/{unit,localization}/*.js --no-coverage -R classic",
|
||||
"test:coverage": "tap ./test/{unit,localization}/*.js --coverage --coverage-report=lcov",
|
||||
"test:integration": "npm run test:integration:jest && npm run test:smoke",
|
||||
"test:integration:jest": "jest ./test/integration/*.test.js",
|
||||
"test:integration:remote": "npm run test:integration:jest && npm run test:smoke:sauce",
|
||||
"test:smoke": "tap ./test/integration-legacy/smoke-testing/*.js --timeout=3600 --no-coverage -R classic",
|
||||
"test:smoke:verbose": "tap ./test/integration-legacy/smoke-testing/*.js --timeout=3600 --no-coverage -R spec",
|
||||
"test:smoke:sauce": "SMOKE_REMOTE=true tap ./test/integration-legacy/smoke-testing/*.js --timeout=60000 --no-coverage -R classic",
|
||||
"test:unit": "npm run test:unit:jest && npm run test:unit:tap",
|
||||
"test:unit:jest": "jest ./test/unit/*.test.js && jest ./test/localization/*.test.js",
|
||||
"test:unit:tap": "tap ./test/{unit-legacy,localization-legacy}/*.js --no-coverage -R classic",
|
||||
"test:coverage": "tap ./test/{unit-legacy,localization-legacy}/*.js --coverage --coverage-report=lcov",
|
||||
"build": "npm run clean && npm run translate && webpack --bail",
|
||||
"clean": "rm -rf ./build && rm -rf ./intl && mkdir -p build && mkdir -p intl",
|
||||
"deploy": "npm run deploy:s3 && npm run deploy:fastly",
|
||||
|
@ -60,6 +65,7 @@
|
|||
"babel-preset-react": "6.22.0",
|
||||
"bowser": "1.9.4",
|
||||
"cheerio": "1.0.0-rc.2",
|
||||
"chromedriver": "75.1.0",
|
||||
"classnames": "2.2.5",
|
||||
"cookie": "0.2.2",
|
||||
"copy-webpack-plugin": "0.2.0",
|
||||
|
@ -81,6 +87,7 @@
|
|||
"google-libphonenumber": "3.2.3",
|
||||
"html-webpack-plugin": "2.22.0",
|
||||
"iso-3166-2": "0.4.0",
|
||||
"jest": "^23.6.0",
|
||||
"json-loader": "0.5.2",
|
||||
"json2po-stream": "1.0.3",
|
||||
"keymirror": "0.1.1",
|
||||
|
@ -115,6 +122,7 @@
|
|||
"sass-loader": "6.0.6",
|
||||
"scratch-gui": "0.1.0-prerelease.20190718181300",
|
||||
"scratch-l10n": "latest",
|
||||
"selenium-webdriver": "3.6.0",
|
||||
"slick-carousel": "1.6.0",
|
||||
"source-map-support": "0.3.2",
|
||||
"style-loader": "0.12.3",
|
||||
|
|
5
test/.eslintrc.js
Normal file
5
test/.eslintrc.js
Normal file
|
@ -0,0 +1,5 @@
|
|||
module.exports = {
|
||||
env: {
|
||||
jest: true
|
||||
}
|
||||
};
|
|
@ -12,10 +12,10 @@
|
|||
* Tests can be run using Saucelabs, an online service that can test browser/os combinations remotely. Currently all tests are written for use for chrome on mac.
|
||||
|
||||
## Using tap
|
||||
* Run all tests in the smoke-testing directory from the command-line: `$ SMOKE_USERNAME=username SMOKE_PASSWORD=password ROOT_URL=https://scratch.mit.edu npm run test:smoke`
|
||||
* Run all tests in the smoke-testing directory from the command-line: `$ SMOKE_USERNAME=username SMOKE_PASSWORD=password ROOT_URL=https://scratch.mit.edu npm run smoke`
|
||||
* To run a single file from the command-line: `$ SMOKE_USERNAME=username SMOKE_PASSWORD=password ROOT_URL=https://scratch.mit.edu node_modules/.bin/tap ./test/integration/smoke-testing/filename.js --timeout=3600`
|
||||
* The timeout var is for the length of the entire tap test-suite; if you are getting a timeout error, you may need to adjust this value (some of the Selenium tests take a while to run)
|
||||
* To run tests using saucelabs run this command `$ SMOKE_USERNAME=username SMOKE_PASSWORD=password SAUCE_USERNAME=saucelabsUsername SAUCE_ACCESS_KEY=saucelabsAccessKey ROOT_URL=https://scratch.mit.edu npm run test:smoke:sauce`
|
||||
* To run tests using saucelabs run this command `$ SMOKE_USERNAME=username SMOKE_PASSWORD=password SAUCE_USERNAME=saucelabsUsername SAUCE_ACCESS_KEY=saucelabsAccessKey ROOT_URL=https://scratch.mit.edu npm run smoke-sauce`
|
||||
|
||||
|
||||
### Configuration
|
||||
|
@ -25,7 +25,7 @@
|
|||
| `ROOT_URL` | `scratch.ly` | Location you want to run the tests against |
|
||||
| `SMOKE_USERNAME` | `None` | Username for Scratch user you're signing in with to test |
|
||||
| `SMOKE_PASSWORD` | `None` | Password for Scratch user you're signing in with to test |
|
||||
| `SMOKE_REMOTE` | `false` | Tests with Sauce Labs or not. True if running test:smoke:sauce |
|
||||
| `SMOKE_REMOTE` | `false` | Tests with Sauce Labs or not. True if running smoke-sauce |
|
||||
| `SMOKE_HEADLESS` | `false` | Run browser in headless mode. Flaky at the moment |
|
||||
| `SAUCE_USERNAME` | `None` | Username for your Sauce Labs account |
|
||||
| `SAUCE_ACCESS_KEY` | `None` | Access Key for Sauce Labs found under User Settings |
|
168
test/integration-legacy/selenium-helpers.js
Normal file
168
test/integration-legacy/selenium-helpers.js
Normal file
|
@ -0,0 +1,168 @@
|
|||
const webdriver = require('selenium-webdriver');
|
||||
const bindAll = require('lodash.bindall');
|
||||
require('chromedriver');
|
||||
|
||||
const headless = process.env.SMOKE_HEADLESS || false;
|
||||
const remote = process.env.SMOKE_REMOTE || false;
|
||||
const ci = process.env.CI || false;
|
||||
const buildID = process.env.TRAVIS_BUILD_NUMBER;
|
||||
const {SAUCE_USERNAME, SAUCE_ACCESS_KEY} = process.env;
|
||||
const {By, Key, until} = webdriver;
|
||||
|
||||
const DEFAULT_TIMEOUT_MILLISECONDS = 20 * 1000;
|
||||
|
||||
class SeleniumHelper {
|
||||
constructor () {
|
||||
bindAll(this, [
|
||||
'buildDriver',
|
||||
'clickButton',
|
||||
'clickCss',
|
||||
'clickText',
|
||||
'clickXpath',
|
||||
'dragFromXpathToXpath',
|
||||
'findByCss',
|
||||
'findByXpath',
|
||||
'findText',
|
||||
'getKey',
|
||||
'getDriver',
|
||||
'getLogs',
|
||||
'getSauceDriver',
|
||||
'urlMatches',
|
||||
'waitUntilGone'
|
||||
]);
|
||||
}
|
||||
buildDriver (name) {
|
||||
if (remote === 'true'){
|
||||
let nameToUse;
|
||||
if (ci === 'true'){
|
||||
nameToUse = 'travis ' + buildID + ' : ' + name;
|
||||
} else {
|
||||
nameToUse = name;
|
||||
}
|
||||
this.driver = this.getSauceDriver(SAUCE_USERNAME, SAUCE_ACCESS_KEY, nameToUse);
|
||||
} else {
|
||||
this.driver = this.getDriver();
|
||||
}
|
||||
return this.driver;
|
||||
}
|
||||
|
||||
getDriver () {
|
||||
const chromeCapabilities = webdriver.Capabilities.chrome();
|
||||
let args = [];
|
||||
if (headless) {
|
||||
args.push('--headless');
|
||||
args.push('window-size=1024,1680');
|
||||
args.push('--no-sandbox');
|
||||
}
|
||||
chromeCapabilities.set('chromeOptions', {args});
|
||||
let driver = new webdriver.Builder()
|
||||
.forBrowser('chrome')
|
||||
.withCapabilities(chromeCapabilities)
|
||||
.build();
|
||||
return driver;
|
||||
}
|
||||
|
||||
getSauceDriver (username, accessKey, name) {
|
||||
// Driver configs can be generated with the Sauce Platform Configurator
|
||||
// https://wiki.saucelabs.com/display/DOCS/Platform+Configurator
|
||||
let driverConfig = {
|
||||
browserName: 'chrome',
|
||||
platform: 'macOS 10.14',
|
||||
version: '75.0'
|
||||
};
|
||||
var driver = new webdriver.Builder()
|
||||
.withCapabilities({
|
||||
browserName: driverConfig.browserName,
|
||||
platform: driverConfig.platform,
|
||||
version: driverConfig.version,
|
||||
username: username,
|
||||
accessKey: accessKey,
|
||||
name: name
|
||||
})
|
||||
.usingServer(`http://${username}:${accessKey
|
||||
}@ondemand.saucelabs.com:80/wd/hub`)
|
||||
.build();
|
||||
return driver;
|
||||
}
|
||||
|
||||
getKey (keyName) {
|
||||
return Key[keyName];
|
||||
}
|
||||
|
||||
findByXpath (xpath, timeoutMessage = `findByXpath timed out for path: ${xpath}`) {
|
||||
return this.driver.wait(until.elementLocated(By.xpath(xpath)), DEFAULT_TIMEOUT_MILLISECONDS, timeoutMessage)
|
||||
.then(el => (
|
||||
this.driver.wait(el.isDisplayed(), DEFAULT_TIMEOUT_MILLISECONDS, `${xpath} is not visible`)
|
||||
.then(() => el)
|
||||
));
|
||||
}
|
||||
|
||||
waitUntilGone (element) {
|
||||
return this.driver.wait(until.stalenessOf(element));
|
||||
}
|
||||
|
||||
clickXpath (xpath) {
|
||||
return this.findByXpath(xpath).then(el => el.click());
|
||||
}
|
||||
|
||||
clickText (text) {
|
||||
return this.clickXpath(`//*[contains(text(), '${text}')]`);
|
||||
}
|
||||
|
||||
findText (text) {
|
||||
return this.driver.wait(until.elementLocated(By.xpath(`//*[contains(text(), '${text}')]`), 5 * 1000));
|
||||
}
|
||||
|
||||
clickButton (text) {
|
||||
return this.clickXpath(`//button[contains(text(), '${text}')]`);
|
||||
}
|
||||
|
||||
findByCss (css) {
|
||||
return this.driver.wait(until.elementLocated(By.css(css), 1000 * 5));
|
||||
}
|
||||
|
||||
clickCss (css) {
|
||||
return this.findByCss(css).then(el => el.click());
|
||||
}
|
||||
|
||||
dragFromXpathToXpath (startXpath, endXpath) {
|
||||
return this.findByXpath(startXpath).then(startEl => {
|
||||
return this.findByXpath(endXpath).then(endEl => {
|
||||
return this.driver.actions()
|
||||
.dragAndDrop(startEl, endEl)
|
||||
.perform();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
urlMatches (regex) {
|
||||
return this.driver.wait(until.urlMatches(regex), 1000 * 5);
|
||||
}
|
||||
|
||||
getLogs (whitelist) {
|
||||
return this.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;
|
||||
}
|
||||
return true;
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
module.exports = SeleniumHelper;
|
|
@ -33,18 +33,24 @@ tap.tearDown(function () {
|
|||
});
|
||||
|
||||
tap.beforeEach(function () {
|
||||
return driver.get(url);
|
||||
});
|
||||
|
||||
test('Sign in to Scratch using scratchr2 navbar', t => {
|
||||
clickText('Sign in')
|
||||
return driver.get(url)
|
||||
.then(() => clickText('Sign in'))
|
||||
.then(() => findByXpath('//input[@id="login_dropdown_username"]'))
|
||||
.then((element) => element.sendKeys(username))
|
||||
.then(() => findByXpath('//input[@name="password"]'))
|
||||
.then((element) => element.sendKeys(password))
|
||||
.then(() => clickButton('Sign in'))
|
||||
.then(() => findByXpath('//li[contains(@class, "logged-in-user")' +
|
||||
'and contains(@class, "dropdown")]/span'))
|
||||
.then(() => clickButton('Sign in'));
|
||||
});
|
||||
|
||||
tap.afterEach(function () {
|
||||
return clickXpath('//span[@class="user-name dropdown-toggle"]')
|
||||
.then(() => clickXpath('//li[@id="logout"] '))
|
||||
.then(() => findByXpath('//div[@class="title-banner intro-banner"]'));
|
||||
});
|
||||
|
||||
test('Sign in to Scratch using scratchr2 navbar', t => {
|
||||
findByXpath('//li[contains(@class, "logged-in-user")' +
|
||||
'and contains(@class, "dropdown")]/span')
|
||||
.then((element) => element.getText('span'))
|
||||
.then((text) => t.match(text.toLowerCase(), username.substring(0, 10).toLowerCase(),
|
||||
'first part of username should be displayed in navbar'))
|
||||
|
@ -85,6 +91,7 @@ test('clicking See Inside should take you to the editor', t => {
|
|||
var expectedUrl = '/editor';
|
||||
t.equal(u.substr(-expectedUrl.length), expectedUrl, 'after clicking, the URL should end in #editor');
|
||||
})
|
||||
.then(() => driver.get(url))
|
||||
.then(() => t.end());
|
||||
});
|
||||
|
||||
|
@ -96,6 +103,7 @@ test('clicking a project title should take you to the project page', t => {
|
|||
var expectedUrlRegExp = new RegExp('/projects/.*[0-9].*/?');
|
||||
t.match(u, expectedUrlRegExp, 'after clicking, the URL should end in projects/PROJECT_ID/');
|
||||
})
|
||||
.then(() => driver.get(url))
|
||||
.then(() => t.end());
|
||||
});
|
||||
|
|
@ -1,6 +0,0 @@
|
|||
{
|
||||
"dependencies": {
|
||||
"selenium-webdriver": "3.6.0",
|
||||
"chromedriver": "2.43.1"
|
||||
}
|
||||
}
|
5
test/integration/test-integration.test.js
Normal file
5
test/integration/test-integration.test.js
Normal file
|
@ -0,0 +1,5 @@
|
|||
describe('test jest integration', () => {
|
||||
test('testing test', () => {
|
||||
expect('integration').toEqual('integration');
|
||||
});
|
||||
});
|
5
test/localization/test-localization.test.js
Normal file
5
test/localization/test-localization.test.js
Normal file
|
@ -0,0 +1,5 @@
|
|||
describe('test jest localization', () => {
|
||||
test('testing localization test', () => {
|
||||
expect('localization').toEqual('localization');
|
||||
});
|
||||
});
|
5
test/unit/test-unit.test.js
Normal file
5
test/unit/test-unit.test.js
Normal file
|
@ -0,0 +1,5 @@
|
|||
describe('test jest unit', () => {
|
||||
test('testing test', () => {
|
||||
expect('unit').toEqual('unit');
|
||||
});
|
||||
});
|
Loading…
Reference in a new issue