initial import for Open Source 🎉
This commit is contained in:
parent
1898c361f3
commit
9c0dd3b722
2048 changed files with 218743 additions and 0 deletions
53
integration_tests/README.md
Normal file
53
integration_tests/README.md
Normal 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
|
||||
|
132
integration_tests/package.json
Normal file
132
integration_tests/package.json
Normal 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"
|
||||
}
|
||||
}
|
||||
|
139
integration_tests/protractor.conf.ts
Normal file
139
integration_tests/protractor.conf.ts
Normal 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;
|
||||
};
|
||||
};
|
49
integration_tests/tests/quay-login.scenario.ts
Normal file
49
integration_tests/tests/quay-login.scenario.ts
Normal 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();
|
||||
});
|
||||
});
|
||||
});
|
26
integration_tests/tsconfig.json
Normal file
26
integration_tests/tsconfig.json
Normal 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"
|
||||
]
|
||||
}
|
28
integration_tests/views/quay-login.view.ts
Normal file
28
integration_tests/views/quay-login.view.ts
Normal 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')));
|
||||
};
|
Reference in a new issue