WIP: Move test to cypress.io
This is mostly just a PoC to see if our CI infrastructure is able to run cypress.io tests. Changing language currently triggers a "500 Parse Error" in cypress, which needs to be fixed in Cockpit.
This commit is contained in:
parent
5d73df67ee
commit
caf96475e2
7 changed files with 134 additions and 65 deletions
3
.gitignore
vendored
3
.gitignore
vendored
|
|
@ -4,12 +4,11 @@
|
||||||
*.rpm
|
*.rpm
|
||||||
node_modules/
|
node_modules/
|
||||||
dist/
|
dist/
|
||||||
|
cypress/support
|
||||||
/*.spec
|
/*.spec
|
||||||
/.vagrant
|
/.vagrant
|
||||||
package-lock.json
|
package-lock.json
|
||||||
Test*FAIL*
|
|
||||||
bots/
|
bots/
|
||||||
test/common/
|
|
||||||
test/images/
|
test/images/
|
||||||
*.pot
|
*.pot
|
||||||
POTFILES*
|
POTFILES*
|
||||||
|
|
|
||||||
11
Makefile
11
Makefile
|
|
@ -126,8 +126,8 @@ vm: $(VM_IMAGE)
|
||||||
echo $(VM_IMAGE)
|
echo $(VM_IMAGE)
|
||||||
|
|
||||||
# run the browser integration tests; skip check for SELinux denials
|
# run the browser integration tests; skip check for SELinux denials
|
||||||
check: $(NODE_MODULES_TEST) $(VM_IMAGE) test/common
|
check: $(NODE_MODULES_TEST) $(VM_IMAGE)
|
||||||
TEST_AUDIT_NO_SELINUX=1 test/check-application
|
npm run cypress
|
||||||
|
|
||||||
# checkout Cockpit's bots/ directory for standard test VM images and API to launch them
|
# checkout Cockpit's bots/ directory for standard test VM images and API to launch them
|
||||||
# must be from cockpit's master, as only that has current and existing images; but testvm.py API is stable
|
# must be from cockpit's master, as only that has current and existing images; but testvm.py API is stable
|
||||||
|
|
@ -136,13 +136,6 @@ bots:
|
||||||
git checkout --force FETCH_HEAD -- bots/
|
git checkout --force FETCH_HEAD -- bots/
|
||||||
git reset bots
|
git reset bots
|
||||||
|
|
||||||
# checkout Cockpit's test API; this has no API stability guarantee, so check out a stable tag
|
|
||||||
# when you start a new project, use the latest relese, and update it from time to time
|
|
||||||
test/common:
|
|
||||||
git fetch --depth=1 https://github.com/cockpit-project/cockpit.git 176
|
|
||||||
git checkout --force FETCH_HEAD -- test/common
|
|
||||||
git reset test/common
|
|
||||||
|
|
||||||
$(NODE_MODULES_TEST): package.json
|
$(NODE_MODULES_TEST): package.json
|
||||||
npm install
|
npm install
|
||||||
|
|
||||||
|
|
|
||||||
4
cypress.json
Normal file
4
cypress.json
Normal file
|
|
@ -0,0 +1,4 @@
|
||||||
|
{
|
||||||
|
"defaultCommandTimeout": 10000,
|
||||||
|
"video": false
|
||||||
|
}
|
||||||
54
cypress/integration/application.js
Normal file
54
cypress/integration/application.js
Normal file
|
|
@ -0,0 +1,54 @@
|
||||||
|
// Use this for skipping the login page, i. e. all tests which do not test the login page itself
|
||||||
|
const visit_opts = { auth: { username: 'admin', password: 'foobar' } };
|
||||||
|
|
||||||
|
describe('Application', () => {
|
||||||
|
beforeEach('start VM', function () {
|
||||||
|
cy.task('startVM').then(url => Cypress.config('baseUrl', url));
|
||||||
|
|
||||||
|
// Programmatically enable the "Reuse my password for privileged tasks" option
|
||||||
|
cy.server({
|
||||||
|
onAnyRequest: function (route, proxy) {
|
||||||
|
proxy.xhr.setRequestHeader('X-Authorize', 'password');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach('stop VM', function() {
|
||||||
|
cy.task('stopVM');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('basic functionality', function () {
|
||||||
|
// cypress doesn't handle frames, so go to specific frame
|
||||||
|
cy.visit('/cockpit/@localhost/starter-kit/index.html', visit_opts)
|
||||||
|
// verify expected heading
|
||||||
|
cy.get('.container-fluid h2').should('contain', 'Starter Kit');
|
||||||
|
// verify expected host name
|
||||||
|
cy.task('runVM', 'cat /etc/hostname').then(out => {
|
||||||
|
cy.get('.container-fluid p').should('contain', 'Running on ' + out.trim());
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('test with German translations', function() {
|
||||||
|
cy.visit('/', visit_opts);
|
||||||
|
|
||||||
|
// change language in menu
|
||||||
|
cy.get('#content-user-name').click();
|
||||||
|
cy.get('.display-language-menu a').click();
|
||||||
|
cy.get('#display-language select').select('de-de');
|
||||||
|
|
||||||
|
// HACK: language switching in Chrome not working in current session (Cockpit issue #8160)
|
||||||
|
cy.on('uncaught:exception', (err, runnable) => {
|
||||||
|
cy.log("Uncaught exception:", err);
|
||||||
|
return false;
|
||||||
|
});
|
||||||
|
|
||||||
|
// menu label (from manifest) should be translated
|
||||||
|
cy.get('#display-language-select-button').click();
|
||||||
|
cy.get("#host-apps a[href='/starter-kit']").should('contain', 'Bausatz');
|
||||||
|
|
||||||
|
// page label (from js) should be translated
|
||||||
|
cy.visit('/cockpit/@localhost/starter-kit/index.html');
|
||||||
|
cy.get('.container-fluid p').should('contain', 'Läuft auf');
|
||||||
|
});
|
||||||
|
|
||||||
|
})
|
||||||
69
cypress/plugins/index.js
Normal file
69
cypress/plugins/index.js
Normal file
|
|
@ -0,0 +1,69 @@
|
||||||
|
const child_process = require("child_process");
|
||||||
|
|
||||||
|
var vm_proc, ssh_args, cockpit_url;
|
||||||
|
|
||||||
|
// poor man's polling implementation
|
||||||
|
function waitpid(pid) {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
function check() {
|
||||||
|
try {
|
||||||
|
process.kill(pid);
|
||||||
|
// succeeds → process still exists, poll again
|
||||||
|
setTimeout(check, 50);
|
||||||
|
} catch(e) {
|
||||||
|
// ESRCH → process is gone
|
||||||
|
if (e.code == "ESRCH")
|
||||||
|
resolve(null);
|
||||||
|
else
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
check();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = (on, config) => {
|
||||||
|
on("task", {
|
||||||
|
startVM: image => {
|
||||||
|
if (!image)
|
||||||
|
image = process.env.TEST_OS || "fedora-29";
|
||||||
|
// already running? happens when cypress restarts the test after visiting a baseUrl the first time
|
||||||
|
if (vm_proc)
|
||||||
|
return cockpit_url;
|
||||||
|
|
||||||
|
// no, start a new VM
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
let proc = child_process.spawn("bots/machine/testvm.py", [image],
|
||||||
|
{ stdio: ["pipe", "pipe", "inherit"] });
|
||||||
|
let buf = "";
|
||||||
|
vm_proc = proc.pid;
|
||||||
|
proc.stdout.on("data", data => {
|
||||||
|
buf += data.toString();
|
||||||
|
if (buf.indexOf("\nRUNNING\n") > 0) {
|
||||||
|
let lines = buf.split("\n");
|
||||||
|
ssh_args = lines[0].split(" ").slice(1);
|
||||||
|
cockpit_url = lines[1];
|
||||||
|
resolve(cockpit_url);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
proc.on("error", err => reject (err));
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
stopVM: () => {
|
||||||
|
process.kill(vm_proc);
|
||||||
|
let p = waitpid(vm_proc);
|
||||||
|
p.then(() => { vm_proc = null; });
|
||||||
|
return p;
|
||||||
|
},
|
||||||
|
|
||||||
|
runVM: command => {
|
||||||
|
res = child_process.spawnSync("ssh", ssh_args.concat(command),
|
||||||
|
{ stdio: ["pipe", "pipe", "inherit"], encoding: "UTF-8" });
|
||||||
|
if (res.status)
|
||||||
|
throw new Error(`Command "${command} failed with code ${res.status}`);
|
||||||
|
return res.stdout;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
@ -9,7 +9,9 @@
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"build": "webpack",
|
"build": "webpack",
|
||||||
"eslint": "eslint --ext .jsx --ext .es6 src/",
|
"eslint": "eslint --ext .jsx --ext .es6 src/",
|
||||||
"eslint:fix": "eslint --fix --ext .jsx --ext .es6 src/"
|
"eslint:fix": "eslint --fix --ext .jsx --ext .es6 src/",
|
||||||
|
"cypress": "cypress run",
|
||||||
|
"cypress:headed": "cypress run --headed"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@babel/core": "^7.0.0",
|
"@babel/core": "^7.0.0",
|
||||||
|
|
@ -17,10 +19,10 @@
|
||||||
"@babel/preset-react": "^7.0.0",
|
"@babel/preset-react": "^7.0.0",
|
||||||
"babel-eslint": "^9.0.0",
|
"babel-eslint": "^9.0.0",
|
||||||
"babel-loader": "^8.0.0",
|
"babel-loader": "^8.0.0",
|
||||||
"chrome-remote-interface": "^0.26.1",
|
|
||||||
"compression-webpack-plugin": "^1.1.11",
|
"compression-webpack-plugin": "^1.1.11",
|
||||||
"copy-webpack-plugin": "^4.5.2",
|
"copy-webpack-plugin": "^4.5.2",
|
||||||
"css-loader": "^0.28.11",
|
"css-loader": "^0.28.11",
|
||||||
|
"cypress": "^3.1.0",
|
||||||
"eslint": "^5.4.0",
|
"eslint": "^5.4.0",
|
||||||
"eslint-config-standard": "^11.0.0",
|
"eslint-config-standard": "^11.0.0",
|
||||||
"eslint-config-standard-react": "^6.0.0",
|
"eslint-config-standard-react": "^6.0.0",
|
||||||
|
|
@ -36,7 +38,6 @@
|
||||||
"jed": "^1.1.1",
|
"jed": "^1.1.1",
|
||||||
"po2json": "^0.4.5",
|
"po2json": "^0.4.5",
|
||||||
"sass-loader": "^7.0.3",
|
"sass-loader": "^7.0.3",
|
||||||
"sizzle": "^2.3.3",
|
|
||||||
"stdio": "^0.2.7",
|
"stdio": "^0.2.7",
|
||||||
"webpack": "^4.17.1",
|
"webpack": "^4.17.1",
|
||||||
"webpack-cli": "^3.1.0"
|
"webpack-cli": "^3.1.0"
|
||||||
|
|
|
||||||
|
|
@ -1,51 +0,0 @@
|
||||||
#!/usr/bin/python3
|
|
||||||
# Run this with --help to see available options for tracing and debugging
|
|
||||||
# See https://github.com/cockpit-project/cockpit/blob/master/test/common/testlib.py
|
|
||||||
# "class Browser" and "class MachineCase" for the available API.
|
|
||||||
|
|
||||||
import os
|
|
||||||
import sys
|
|
||||||
|
|
||||||
# import Cockpit's machinery for test VMs and its browser test API
|
|
||||||
TEST_DIR = os.path.dirname(__file__)
|
|
||||||
sys.path.append(os.path.join(TEST_DIR, "common"))
|
|
||||||
sys.path.append(os.path.join(os.path.dirname(TEST_DIR), "bots/machine"))
|
|
||||||
import testlib
|
|
||||||
|
|
||||||
|
|
||||||
class TestApplication(testlib.MachineCase):
|
|
||||||
def testBasic(self):
|
|
||||||
b = self.browser
|
|
||||||
m = self.machine
|
|
||||||
|
|
||||||
self.login_and_go("/starter-kit")
|
|
||||||
# verify expected heading
|
|
||||||
b.wait_present(".container-fluid h2")
|
|
||||||
b.wait_text(".container-fluid h2", "Starter Kit")
|
|
||||||
|
|
||||||
# verify expected host name
|
|
||||||
hostname = m.execute("hostname").strip()
|
|
||||||
b.wait_present(".container-fluid p")
|
|
||||||
b.wait_text(".container-fluid p", "Running on " + hostname)
|
|
||||||
|
|
||||||
# change language to German
|
|
||||||
b.switch_to_top()
|
|
||||||
b.click("#content-user-name")
|
|
||||||
b.click(".display-language-menu a")
|
|
||||||
b.wait_popup('display-language')
|
|
||||||
b.set_val("#display-language select", "de-de")
|
|
||||||
b.click("#display-language-select-button")
|
|
||||||
b.expect_load()
|
|
||||||
# HACK: work around language switching in Chrome not working in current session (Cockpit issue #8160)
|
|
||||||
b.reload(ignore_cache=True)
|
|
||||||
b.wait_present("#content")
|
|
||||||
# menu label (from manifest) should be translated
|
|
||||||
b.wait_text("#host-apps a[href='/starter-kit']", "Bausatz")
|
|
||||||
|
|
||||||
b.go("/starter-kit")
|
|
||||||
b.enter_page("/starter-kit")
|
|
||||||
# page label (from js) should be translated
|
|
||||||
b.wait_in_text(".container-fluid p", "Läuft auf")
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
|
||||||
testlib.test_main()
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue