initial import for Open Source 🎉

This commit is contained in:
Jimmy Zelinskie 2019-11-12 11:09:47 -05:00
parent 1898c361f3
commit 9c0dd3b722
2048 changed files with 218743 additions and 0 deletions

View file

@ -0,0 +1,53 @@
Quay Integration Testing
============================
## Dependencies:
1. [node.js](https://nodejs.org/) >= 8 & [yarn](https://yarnpkg.com/en/docs/install) >= 1.3.2
2. Google Chrome/Chromium >= 60 (needs --headless flag) for integration tests
### Install Dependencies
To install the dependencies:
```
yarn install
```
You must run this command once, and every time the dependencies change. `node_modules` are not committed to git.
## Integration Tests
Integration tests are run in a headless Chrome driven by [protractor](http://www.protractortest.org/#/). Requirements include Chrome, a working Quay, podman.
Setup (or any time you change node_modules - `yarn add` or `yarn install`)
```
cd integration_tests && yarn run webdriver-update
```
Run integration tests:
```
yarn run test-all
```
Run integration tests against a specific test suite:
```
yarn run test-suite --suite <test suite>
```
Could check test suite list in package.json.
### Required Environment Varaiable
```
export QUAY_APP_ADDRESS=<Quay Hostname>
export QUAY_INTERNAL_USERNAME=<Username>
export QUAY_INTERNAL_PASSWORD=<Password>
```
## Supported Browsers
Support the latest versions of the following browsers:
- Edge
- Chrome
- Safari
- Firefox

View file

@ -0,0 +1,132 @@
{
"description": "Quay Integration Testing",
"license": "UNLICENSED",
"repository": {
"type" : "git",
"url" : "https://github.com/quay/quay.git",
"directory": "integration_tests"
},
"homepage": "https://github.com/quay/quay/blob/master/integration_tests/README.md",
"private": true,
"scripts": {
"lint": "eslint --ext .js,.jsx,.ts,.tsx --color .",
"webdriver-update": "webdriver-manager update --gecko=false",
"test-all-tap": "TAP=true yarn run test-gui",
"test-all": "yarn run test-suite --suite all",
"test-suite": "ts-node -O '{\"module\":\"commonjs\"}' ./node_modules/.bin/protractor protractor.conf.ts",
"debug-test-suite": "TS_NODE_COMPILER_OPTIONS='{\"module\":\"commonjs\"}' node -r ts-node/register --inspect-brk ./node_modules/.bin/protractor protractor.conf.ts"
},
"dependencies": {
"@patternfly/patternfly": "2.6.7",
"@patternfly/react-charts": "^3.4.5",
"@patternfly/react-core": "3.16.16",
"brace": "0.11.x",
"classnames": "2.x",
"core-js": "2.x",
"file-saver": "1.3.x",
"font-awesome": "4.7.x",
"fuzzysearch": "1.0.x",
"history": "4.x",
"immutable": "3.x",
"js-base64": "^2.5.1",
"js-yaml": "3.x",
"lodash-es": "4.x",
"memoize-one": "5.x",
"murmurhash-js": "1.0.x",
"openshift-logos-icon": "1.7.1",
"patternfly": "^3.59.1",
"patternfly-react": "2.32.3",
"patternfly-react-extensions": "2.18.4",
"plotly.js": "1.47.3",
"prop-types": "15.6.x",
"react": "16.8.6",
"react-copy-to-clipboard": "5.x",
"react-dnd": "^2.6.0",
"react-dnd-html5-backend": "^2.6.0",
"react-dom": "16.8.6",
"react-helmet": "5.x",
"react-jsonschema-form": "^1.0.4",
"react-lightweight-tooltip": "1.x",
"react-modal": "3.x",
"react-redux": "5.x",
"react-router-dom": "4.x",
"react-tagsinput": "3.19.x",
"react-transition-group": "2.3.x",
"react-virtualized": "9.x",
"redux": "4.0.1",
"sanitize-html": "1.x",
"screenfull": "4.x",
"semver": "^6.0.0",
"showdown": "1.8.x",
"text-encoding": "0.x",
"typesafe-actions": "^4.2.1",
"url-polyfill": "^1.1.5",
"url-search-params-polyfill": "2.x",
"whatwg-fetch": "2.x",
"xterm": "^3.12.2"
},
"devDependencies": {
"@types/classnames": "^2.2.7",
"@types/enzyme": "3.x",
"@types/glob": "7.x",
"@types/immutable": "3.x",
"@types/jasmine": "2.8.x",
"@types/jasminewd2": "2.0.x",
"@types/lodash-es": "4.x",
"@types/prop-types": "15.5.6",
"@types/react": "16.8.13",
"@types/react-dom": "16.8.4",
"@types/react-helmet": "5.x",
"@types/react-jsonschema-form": "^1.0.8",
"@types/react-redux": "6.0.2",
"@types/react-router-dom": "4.2.7",
"@types/react-transition-group": "2.x",
"@types/react-virtualized": "9.x",
"@types/webpack": "4.x",
"@typescript-eslint/eslint-plugin": "^1.7.0",
"@typescript-eslint/parser": "^1.7.0",
"bootstrap-sass": "^3.3.7",
"cache-loader": "1.x",
"chalk": "2.3.x",
"chromedriver": "^2.43.3",
"circular-dependency-plugin": "5.0.2",
"css-loader": "0.28.x",
"enzyme": "^3.9.0",
"enzyme-adapter-react-16": "1.12.1",
"eslint": "5.16.0",
"eslint-plugin-import": "2.x",
"eslint-plugin-react": "7.x",
"eslint-plugin-react-hooks": "^1.6.0",
"file-loader": "1.x",
"fork-ts-checker-webpack-plugin": "0.x",
"glob": "7.x",
"glslify-loader": "1.x",
"html-webpack-plugin": "3.x",
"jasmine-console-reporter": "2.x",
"jasmine-core": "2.x",
"jasmine-reporters": "2.x",
"jest": "21.x",
"jest-cli": "21.x",
"mini-css-extract-plugin": "0.4.x",
"node-sass": "4.8.x",
"protractor": "5.4.x",
"protractor-fail-fast": "3.x",
"protractor-jasmine2-screenshot-reporter": "0.5.x",
"read-pkg": "5.x",
"resolve-url-loader": "2.x",
"sass-loader": "6.x",
"thread-loader": "1.x",
"ts-jest": "21.x",
"ts-loader": "5.3.3",
"ts-node": "5.x",
"typescript": "3.4.4",
"webpack": "4.29.6",
"webpack-bundle-analyzer": "2.x",
"webpack-cli": "^2.0.12",
"webpack-virtual-modules": "^0.1.10"
},
"engines": {
"node": ">=8.x"
}
}

View file

@ -0,0 +1,139 @@
import { Config, browser, logging } from 'protractor';
import { execSync } from 'child_process';
import * as HtmlScreenshotReporter from 'protractor-jasmine2-screenshot-reporter';
import * as _ from 'lodash';
import { TapReporter, JUnitXmlReporter } from 'jasmine-reporters';
import * as ConsoleReporter from 'jasmine-console-reporter';
import * as failFast from 'protractor-fail-fast';
import { createWriteStream } from 'fs';
import { format } from 'util';
const tap = !!process.env.TAP;
export const BROWSER_TIMEOUT = 15000;
export const appHost = `${process.env.QUAY_APP_ADDRESS}${(process.env.QUAY_BASE_PATH || '/').replace(/\/$/, '')}`;
export const testName = `test-${Math.random().toString(36).replace(/[^a-z]+/g, '').substr(0, 5)}`;
const htmlReporter = new HtmlScreenshotReporter({ dest: './gui_test_screenshots', inlineImages: true, captureOnlyFailedSpecs: true, filename: 'test-gui-report.html' });
const junitReporter = new JUnitXmlReporter({ savePath: './gui_test_screenshots', consolidateAll: true });
const browserLogs: logging.Entry[] = [];
//const suite = (tests: string[]) => (!_.isNil(process.env.BRIDGE_KUBEADMIN_PASSWORD) ? ['tests/login.scenario.ts'] : []).concat(['tests/base.scenario.ts', ...tests]);
const suite = (tests: string[]) => (['tests/base.scenario.ts', ...tests]);
export const config: Config = {
framework: 'jasmine',
directConnect: true,
skipSourceMapSupport: true,
jasmineNodeOpts: {
print: () => null,
defaultTimeoutInterval: 40000,
},
logLevel: tap ? 'ERROR' : 'INFO',
plugins: [failFast.init()],
capabilities: {
browserName: 'chrome',
acceptInsecureCerts: true,
chromeOptions: {
args: [
'--disable-gpu',
// '--headless',
'--no-sandbox',
'--window-size=1920,1200',
'--disable-background-timer-throttling',
'--disable-renderer-backgrounding',
'--disable-raf-throttling',
// Avoid crashes when running in a container due to small /dev/shm size
// https://bugs.chromium.org/p/chromium/issues/detail?id=715363
'--disable-dev-shm-usage',
],
prefs: {
'profile.password_manager_enabled': false,
'credentials_enable_service': false,
'password_manager_enabled': false,
},
},
/**
* 'browserName': 'firefox',
* 'moz:firefoxOptions': {
* 'args': ['--safe-mode']
* }
*/
},
beforeLaunch: () => new Promise(resolve => htmlReporter.beforeLaunch(resolve)),
onPrepare: () => {
browser.waitForAngularEnabled(false);
jasmine.getEnv().addReporter(htmlReporter);
jasmine.getEnv().addReporter(junitReporter);
if (tap) {
jasmine.getEnv().addReporter(new TapReporter());
} else {
jasmine.getEnv().addReporter(new ConsoleReporter());
}
},
onComplete: async() => {
const consoleLogStream = createWriteStream('gui_test_screenshots/browser.log', { flags: 'a' });
browserLogs.forEach(log => {
const { level, message } = log;
const messageStr = _.isArray(message) ? message.join(' ') : message;
consoleLogStream.write(`${format.apply(null, [`[${level.name}]`, messageStr])}\n`);
});
const url = await browser.getCurrentUrl();
console.log('Last browser URL: ', url);
await browser.close();
// should add a step to clean up organizations and repos created during
// testing
// execSync(`if kubectl get ${resource} ${testName} 2> /dev/null; then kubectl delete ${resource} ${testName}; fi`);
},
onCleanUp: (exitCode) => {
return console.log('Cleaning up completed');
},
afterLaunch: (exitCode) => {
failFast.clean();
return new Promise(resolve => htmlReporter.afterLaunch(resolve.bind(this, exitCode)));
},
suites: {
filter: suite([
'tests/filter.scenario.ts',
]),
login: [
'tests/login.scenario.ts',
],
quaylogin: [
'tests/quay-login.scenario.ts',
],
},
};
export const checkLogs = async() => (await browser.manage().logs().get('browser'))
.map(log => {
browserLogs.push(log);
return log;
});
function hasError() {
return window.windowError;
}
export const checkErrors = async() => await browser.executeScript(hasError).then(err => {
if (err) {
fail(`omg js error: ${err}`);
}
});
export const waitForCount = (elementArrayFinder, expectedCount) => {
return async() => {
const actualCount = await elementArrayFinder.count();
return expectedCount >= actualCount;
};
};
export const waitForNone = (elementArrayFinder) => {
return async() => {
const count = await elementArrayFinder.count();
return count === 0;
};
};

View file

@ -0,0 +1,49 @@
//import { $, browser, ExpectedConditions as until } from 'protractor';
import { $, browser } from 'protractor';
import { appHost } from '../protractor.conf';
import * as loginView from '../views/quay-login.view';
//import * as sidenavView from '../views/sidenav.view';
//import * as clusterSettingsView from '../views/cluster-settings.view';
const JASMINE_DEFAULT_TIMEOUT_INTERVAL = jasmine.DEFAULT_TIMEOUT_INTERVAL;
const JASMINE_EXTENDED_TIMEOUT_INTERVAL = 1000 * 60 * 3;
//const KUBEADMIN_IDP = 'kube:admin';
//const KUBEADMIN_USERNAME = 'kubeadmin';
const {
// BRIDGE_HTPASSWD_IDP = 'test',
QUAY_INTERNAL_USERNAME = 'test',
QUAY_INTERNAL_PASSWORD = 'test',
// BRIDGE_KUBEADMIN_PASSWORD,
} = process.env;
describe('Auth test', () => {
beforeAll(async() => {
await browser.get(appHost);
await browser.sleep(3000); // Wait long enough for the login redirect to complete
});
describe('Login test', async() => {
beforeAll(() => {
// Extend the default jasmine timeout interval just in case it takes a while for the htpasswd idp to be ready
jasmine.DEFAULT_TIMEOUT_INTERVAL = JASMINE_EXTENDED_TIMEOUT_INTERVAL;
});
afterAll(() => {
// Set jasmine timeout interval back to the original value after these tests are done
jasmine.DEFAULT_TIMEOUT_INTERVAL = JASMINE_DEFAULT_TIMEOUT_INTERVAL;
});
it('logs in', async() => {
await loginView.login(QUAY_INTERNAL_USERNAME, QUAY_INTERNAL_PASSWORD);
expect(browser.getCurrentUrl()).toContain(appHost);
expect(loginView.userDropdown.getText()).toContain(QUAY_INTERNAL_USERNAME);
});
it('logs out', async() => {
await loginView.logout();
expect(browser.getCurrentUrl()).toContain('repository');
expect($('.user-view').isPresent()).toBeTruthy();
});
});
});

View file

@ -0,0 +1,26 @@
{
"compilerOptions": {
"baseUrl": ".",
"module": "esnext",
"moduleResolution": "node",
"outDir": "./build/",
"target": "es5",
"lib": ["dom", "es2015", "es2016.array.include", "es2017.string"],
"jsx": "react",
"allowJs": true,
"downlevelIteration": true,
"experimentalDecorators": true,
"sourceMap": true,
"noUnusedLocals": true
},
"exclude": [
".yarn",
"**/node_modules"
],
"include": [
"**/*.js",
"**/*.jsx",
"**/*.ts",
"**/*.tsx"
]
}

View file

@ -0,0 +1,28 @@
import { $, browser, ExpectedConditions as until, by, element } from 'protractor';
//import { appHost } from '../protractor.conf';
export const nameInput = $('#signin-username'); // signin-username
export const passwordInput = $('#signin-password'); // signin-password
//export const submitButton = $('button[type=submit]');
export const submitButton = element(by.partialButtonText('Sign in to')); //.$('button[type=submit]');
export const logOutLink = element(by.linkText('Sign out all sessions'));
export const userDropdown = $('.dropdown-toggle.user-dropdown.user-view'); //$('[data-toggle=dropdown] .pf-c-dropdown__toggle');
export const login = async(username: string, password: string) => {
/* if (providerName) {
await selectProvider(providerName);
} */
await browser.wait(until.visibilityOf(nameInput));
await nameInput.sendKeys(username);
await passwordInput.sendKeys(password);
await submitButton.click();
await browser.wait(until.presenceOf(userDropdown));
};
export const logout = async() => {
await browser.wait(until.presenceOf(userDropdown));
await userDropdown.click();
await browser.wait(until.presenceOf(logOutLink));
await logOutLink.click();
await browser.wait(until.presenceOf($('.user-view')));
};