mirror of
https://github.com/OliveTin/OliveTin
synced 2026-08-26 12:36:30 +00:00
Migrate integration-test infrastucture from Cypress to Selenium+Mocha (#170)
* cicd: Move cypress to selenium+mocha * cicd: with is not supported unless using an a container * cicd: relative path to mocha * cicd: the integration-tests runner now starts/stops OliveTin * cicd: Knowing the CWD helps debugging * cicd: Headless chrome * cicd: wait for integration-test server to be started * fmt: Mostly fix isses from eslint * cicd: #169 - Test multiple combo boxes * fmt: let should be const * cicd: Remove cypress entirely
This commit is contained in:
@@ -34,28 +34,16 @@ jobs:
|
||||
cache: true
|
||||
|
||||
- name: grpc
|
||||
run: make grpc
|
||||
run: make -w grpc
|
||||
|
||||
- name: make daemon
|
||||
run: make daemon-compile-x64-lin
|
||||
run: make -w daemon-compile-x64-lin
|
||||
|
||||
- name: unit tests
|
||||
run: make daemon-unittests
|
||||
|
||||
- name: ls
|
||||
run: ls && pwd
|
||||
|
||||
- name: run OliveTin for integration tests
|
||||
run: ./OliveTin &
|
||||
run: make -w daemon-unittests
|
||||
|
||||
- name: integration tests
|
||||
uses: cypress-io/github-action@v6
|
||||
with:
|
||||
working-directory: integration-tests
|
||||
spec: cypress/e2e/general/*
|
||||
|
||||
- name: kill OliveTin after integration tests
|
||||
run: killall OliveTin
|
||||
run: cd integration-tests && make -w
|
||||
|
||||
- name: goreleaser
|
||||
uses: goreleaser/goreleaser-action@v4.2.0
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
env:
|
||||
browser: true
|
||||
es2021: true
|
||||
extends: 'eslint:recommended'
|
||||
parserOptions:
|
||||
ecmaVersion: 12
|
||||
sourceType: module
|
||||
rules: {}
|
||||
@@ -0,0 +1,2 @@
|
||||
require:
|
||||
- mochaSetup.mjs
|
||||
@@ -1,6 +1,5 @@
|
||||
cypress:
|
||||
npm install
|
||||
./cypressRun.sh "general"
|
||||
./cypressRun.sh "hiddenNav"
|
||||
default:
|
||||
npm install --no-fund
|
||||
./node_modules/.bin/mocha
|
||||
|
||||
.PHONY: cypress container
|
||||
.PHONY: default
|
||||
|
||||
@@ -0,0 +1,25 @@
|
||||
---
|
||||
listenAddressSingleHTTPFrontend: 0.0.0.0:1337
|
||||
|
||||
logLevel: "DEBUG"
|
||||
checkForUpdates: false
|
||||
|
||||
actions:
|
||||
- title: Ping Google.com
|
||||
shell: ping google.com -c 1
|
||||
icon: ping
|
||||
|
||||
- title: Test multiple dropdowns
|
||||
shell: echo {{ salutation }} {{ person }}
|
||||
icon: ping
|
||||
arguments:
|
||||
- name: salutation
|
||||
choices:
|
||||
- value: Hello
|
||||
- value: Goodbye
|
||||
|
||||
- name: person
|
||||
choices:
|
||||
- value: Alice
|
||||
- value: Bob
|
||||
- value: Dave
|
||||
@@ -1,14 +0,0 @@
|
||||
const { defineConfig } = require('cypress')
|
||||
|
||||
module.exports = defineConfig({
|
||||
screenshotsFolder: 'results/screenshots/',
|
||||
videosFolder: 'results/videos/',
|
||||
e2e: {
|
||||
// We've imported your old cypress plugins here.
|
||||
// You may want to clean this up later by importing these.
|
||||
setupNodeEvents(on, config) {
|
||||
return require('./cypress/plugins/index.js')(on, config)
|
||||
},
|
||||
baseUrl: 'http://localhost:1337',
|
||||
},
|
||||
})
|
||||
@@ -1,21 +0,0 @@
|
||||
describe('Homepage rendering', () => {
|
||||
beforeEach(() => {
|
||||
cy.visit("/")
|
||||
});
|
||||
|
||||
it("Footer contains promo", () => {
|
||||
cy.get('footer').contains("OliveTin")
|
||||
})
|
||||
|
||||
it('Default buttons are rendered', () => {
|
||||
cy.get("#root-group button").should('have.length', 8)
|
||||
})
|
||||
|
||||
/*
|
||||
it('Switcher navigation is visible', () => {
|
||||
cy.get('#section-switcher').then($el => {
|
||||
expect(Cypress.dom.isHidden($el)).to.be.false
|
||||
})
|
||||
})
|
||||
*/
|
||||
});
|
||||
@@ -1,12 +0,0 @@
|
||||
describe('Hidden Footer', () => {
|
||||
beforeEach(() => {
|
||||
cy.visit("/")
|
||||
cy.wait(500)
|
||||
});
|
||||
|
||||
it('Footer is hidden', () => {
|
||||
cy.get('footer').then($el => {
|
||||
expect(Cypress.dom.isHidden($el)).to.be.true
|
||||
})
|
||||
})
|
||||
});
|
||||
@@ -1,16 +0,0 @@
|
||||
describe('Hidden Nav', () => {
|
||||
beforeEach(() => {
|
||||
cy.visit("/")
|
||||
cy.wait(500)
|
||||
});
|
||||
|
||||
it("Footer contains promo", () => {
|
||||
cy.get('footer').contains("OliveTin")
|
||||
})
|
||||
|
||||
it('Switcher navigation is hidden', () => {
|
||||
cy.get('#section-switcher').then($el => {
|
||||
expect(Cypress.dom.isHidden($el)).to.be.true
|
||||
})
|
||||
})
|
||||
});
|
||||
@@ -1,22 +0,0 @@
|
||||
/// <reference types="cypress" />
|
||||
// ***********************************************************
|
||||
// This example plugins/index.js can be used to load plugins
|
||||
//
|
||||
// You can change the location of this file or turn off loading
|
||||
// the plugins file with the 'pluginsFile' configuration option.
|
||||
//
|
||||
// You can read more here:
|
||||
// https://on.cypress.io/plugins-guide
|
||||
// ***********************************************************
|
||||
|
||||
// This function is called when a project is opened or re-opened (e.g. due to
|
||||
// the project's config changing)
|
||||
|
||||
/**
|
||||
* @type {Cypress.PluginConfig}
|
||||
*/
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
module.exports = (on, config) => {
|
||||
// `on` is used to hook into various events Cypress emits
|
||||
// `config` is the resolved Cypress config
|
||||
}
|
||||
@@ -1,25 +0,0 @@
|
||||
// ***********************************************
|
||||
// This example commands.js shows you how to
|
||||
// create various custom commands and overwrite
|
||||
// existing commands.
|
||||
//
|
||||
// For more comprehensive examples of custom
|
||||
// commands please read more here:
|
||||
// https://on.cypress.io/custom-commands
|
||||
// ***********************************************
|
||||
//
|
||||
//
|
||||
// -- This is a parent command --
|
||||
// Cypress.Commands.add('login', (email, password) => { ... })
|
||||
//
|
||||
//
|
||||
// -- This is a child command --
|
||||
// Cypress.Commands.add('drag', { prevSubject: 'element'}, (subject, options) => { ... })
|
||||
//
|
||||
//
|
||||
// -- This is a dual command --
|
||||
// Cypress.Commands.add('dismiss', { prevSubject: 'optional'}, (subject, options) => { ... })
|
||||
//
|
||||
//
|
||||
// -- This will overwrite an existing command --
|
||||
// Cypress.Commands.overwrite('visit', (originalFn, url, options) => { ... })
|
||||
@@ -1,20 +0,0 @@
|
||||
// ***********************************************************
|
||||
// This example support/index.js is processed and
|
||||
// loaded automatically before your test files.
|
||||
//
|
||||
// This is a great place to put global configuration and
|
||||
// behavior that modifies Cypress.
|
||||
//
|
||||
// You can change the location of this file or turn off
|
||||
// automatically serving support files with the
|
||||
// 'supportFile' configuration option.
|
||||
//
|
||||
// You can read more here:
|
||||
// https://on.cypress.io/configuration
|
||||
// ***********************************************************
|
||||
|
||||
// Import commands.js using ES2015 syntax:
|
||||
import './commands'
|
||||
|
||||
// Alternatively you can use CommonJS syntax:
|
||||
// require('./commands')
|
||||
@@ -1,10 +0,0 @@
|
||||
#!/bin/bash
|
||||
|
||||
set -o xtrace
|
||||
|
||||
echo "Running config $1"
|
||||
|
||||
cp -f ./configs/config.$1.yaml ./configs/config.yaml
|
||||
docker start olivetin
|
||||
NO_COLOR=1 ./node_modules/.bin/cypress run --headless -s cypress/integration/$1/* || true
|
||||
docker kill olivetin
|
||||
@@ -1,12 +0,0 @@
|
||||
#!/bin/bash
|
||||
|
||||
# args:
|
||||
# $1: The Vagrant VM to test against. If blank and only one VM is provisioned, it will use that.
|
||||
|
||||
IP=$(vagrant ssh-config $1 | grep HostName | awk '{print $2}')
|
||||
BASE_URL="http://$IP:1337/"
|
||||
|
||||
echo "IP: $IP, BaseURL: $BASE_URL"
|
||||
|
||||
# Only run the general test, as we cannot easily switch out configs in VMs yet.
|
||||
./node_modules/.bin/cypress run --headless -c baseUrl=$BASE_URL -s cypress/e2e/general/*
|
||||
@@ -0,0 +1,18 @@
|
||||
import { Options } from 'selenium-webdriver/chrome.js'
|
||||
import { Builder, Browser } from 'selenium-webdriver'
|
||||
import getRunner from './runner.mjs'
|
||||
|
||||
export async function mochaGlobalSetup () {
|
||||
const options = new Options()
|
||||
options.addArguments('--headless')
|
||||
|
||||
global.webdriver = await new Builder().forBrowser(Browser.CHROME).setChromeOptions(options).build()
|
||||
|
||||
global.runner = getRunner()
|
||||
|
||||
console.log("Runner constructor: " + global.runner.constructor.name)
|
||||
}
|
||||
|
||||
export async function mochaGlobalTeardown () {
|
||||
await global.webdriver.quit()
|
||||
}
|
||||
Generated
+1382
-1238
File diff suppressed because it is too large
Load Diff
@@ -1,15 +1,22 @@
|
||||
{
|
||||
"name": "olivetin-integration-tests",
|
||||
"version": "1.0.0",
|
||||
"repository": "https://github.com/OliveTin/OliveTin-integration-tests",
|
||||
"description": "The cypress WebUI tests",
|
||||
"repository": "https://github.com/OliveTin/OliveTin",
|
||||
"description": "The integration-tests for OliveTin's webui.",
|
||||
"main": "index.js",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"test": "echo \"Error: no test specified\" && exit 1"
|
||||
},
|
||||
"author": "",
|
||||
"license": "ISC",
|
||||
"devDependencies": {
|
||||
"cypress": "^13.2.0"
|
||||
"chai": "^4.3.10",
|
||||
"eslint": "^8.51.0",
|
||||
"mocha": "^10.2.0",
|
||||
"selenium-webdriver": "4.12.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"wait-on": "^7.0.1"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,61 @@
|
||||
import process from 'node:process'
|
||||
import waitOn from 'wait-on'
|
||||
import { spawn } from 'node:child_process'
|
||||
|
||||
let ot = null
|
||||
|
||||
export default function getRunner () {
|
||||
const type = process.env.OLIVETIN_TEST_RUNNER
|
||||
|
||||
console.log('OLIVETIN_TEST_RUNNER env value is: ', type)
|
||||
|
||||
switch (type) {
|
||||
case 'local':
|
||||
return new OliveTinTestRunnerLocalProcess()
|
||||
case 'vm':
|
||||
return null
|
||||
case 'container':
|
||||
return null
|
||||
default:
|
||||
return new OliveTinTestRunnerLocalProcess()
|
||||
}
|
||||
}
|
||||
|
||||
class OliveTinTestRunnerLocalProcess {
|
||||
async start (cfg) {
|
||||
ot = spawn('./../OliveTin', ['-configdir', 'configs/' + cfg + '/'])
|
||||
|
||||
const logStdout = process.env.OLIVETIN_TEST_RUNNER_LOG_STDOUT === '1'
|
||||
|
||||
if (logStdout) {
|
||||
ot.stdout.on('data', (data) => {
|
||||
console.log(`stdout: ${data}`)
|
||||
})
|
||||
|
||||
ot.stderr.on('data', (data) => {
|
||||
console.error(`stderr: ${data}`)
|
||||
})
|
||||
}
|
||||
|
||||
ot.on('close', (code) => {
|
||||
if (code != null) {
|
||||
console.log(`child process exited with code ${code}`)
|
||||
}
|
||||
})
|
||||
|
||||
/*
|
||||
this.server = await startSomeServer({port: process.env.TEST_PORT});
|
||||
console.log(`server running on port ${this.server.port}`);
|
||||
*/
|
||||
|
||||
await waitOn({
|
||||
'resources': ['http://localhost:1337/']
|
||||
})
|
||||
|
||||
return ot
|
||||
}
|
||||
|
||||
async stop () {
|
||||
await ot.kill()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
import {expect} from 'chai';
|
||||
import {By} from 'selenium-webdriver';
|
||||
|
||||
describe('config: general', function () {
|
||||
before(async function () {
|
||||
await runner.start('general')
|
||||
});
|
||||
|
||||
after(async () => {
|
||||
await runner.stop()
|
||||
});
|
||||
|
||||
it('Page title', async function () {
|
||||
await webdriver.get('http://localhost:1337')
|
||||
|
||||
let title = await webdriver.getTitle();
|
||||
expect(title).to.be.equal("OliveTin")
|
||||
})
|
||||
|
||||
it('Footer contains promo', async function () {
|
||||
let ftr = await webdriver.findElement(By.tagName('footer')).getText()
|
||||
|
||||
expect(ftr).to.contain("Documentation")
|
||||
})
|
||||
|
||||
it('Default buttons are rendered', async function() {
|
||||
await webdriver.get('http://localhost:1337')
|
||||
|
||||
// await webdriver.manage().setTimeouts({ implicit: 2000 });
|
||||
|
||||
let buttons = await webdriver.findElement(By.id('root-group')).findElements(By.tagName('button'))
|
||||
|
||||
expect(buttons).to.have.length(6);
|
||||
})
|
||||
})
|
||||
@@ -0,0 +1,21 @@
|
||||
import { expect } from 'chai';
|
||||
|
||||
import { By } from 'selenium-webdriver';
|
||||
|
||||
describe('config: hiddenFooter', function () {
|
||||
before(async function () {
|
||||
await runner.start('hiddenFooter')
|
||||
});
|
||||
|
||||
after(async () => {
|
||||
await runner.stop()
|
||||
});
|
||||
|
||||
it('Check that footer is hidden', async () => {
|
||||
await webdriver.get('http://localhost:1337')
|
||||
|
||||
let footer = await webdriver.findElement(By.tagName('footer'))
|
||||
|
||||
expect(await footer.isDisplayed()).to.be.false
|
||||
})
|
||||
})
|
||||
@@ -0,0 +1,20 @@
|
||||
import {expect} from 'chai';
|
||||
import {By} from 'selenium-webdriver';
|
||||
|
||||
describe('config: hiddenNav', function () {
|
||||
before(async function () {
|
||||
await runner.start('hiddenNav')
|
||||
})
|
||||
|
||||
after(async () => {
|
||||
await runner.stop()
|
||||
})
|
||||
|
||||
it('nav is hidden', async () => {
|
||||
await webdriver.get('http://localhost:1337')
|
||||
|
||||
const toggler = await webdriver.findElement(By.id('sidebar-toggle-wrapper'))
|
||||
|
||||
expect(await toggler.isDisplayed()).to.be.false
|
||||
})
|
||||
})
|
||||
@@ -0,0 +1,34 @@
|
||||
import { expect } from 'chai'
|
||||
import { By, until } from 'selenium-webdriver'
|
||||
import fs from 'node:fs'
|
||||
|
||||
describe('config: multipleDropdowns', function () {
|
||||
before(async function () {
|
||||
await runner.start('multipleDropdowns')
|
||||
})
|
||||
|
||||
after(async () => {
|
||||
await runner.stop()
|
||||
})
|
||||
|
||||
it('Multiple dropdowns are possible', async function() {
|
||||
await webdriver.get('http://localhost:1337')
|
||||
await webdriver.manage().setTimeouts({ implicit: 2000 });
|
||||
|
||||
const button = await webdriver.findElement(By.id('actionButton_bdc45101bbd12c1397557790d9f3e059')).findElement(By.tagName('button'));
|
||||
|
||||
expect(button).to.not.be.undefined;
|
||||
|
||||
await button.click()
|
||||
|
||||
const dialog = await webdriver.findElement(By.id('argument-popup'));
|
||||
|
||||
await webdriver.wait(until.elementIsVisible(dialog), 2000)
|
||||
|
||||
const selects = await dialog.findElements(By.tagName('select'))
|
||||
|
||||
expect(selects).to.have.length(2)
|
||||
expect(await selects[0].findElements(By.tagName('option'))).to.have.length(2)
|
||||
expect(await selects[1].findElements(By.tagName('option'))).to.have.length(3)
|
||||
})
|
||||
})
|
||||
@@ -25,6 +25,7 @@ type webUISettings struct {
|
||||
func findWebuiDir() string {
|
||||
directoriesToSearch := []string{
|
||||
cfg.WebUIDir,
|
||||
"../webui/",
|
||||
"/usr/share/OliveTin/webui/",
|
||||
"/var/www/OliveTin/",
|
||||
"/var/www/olivetin/",
|
||||
|
||||
+2
-2
@@ -70,7 +70,7 @@
|
||||
</p>
|
||||
</footer>
|
||||
|
||||
<dialog id = "executionResults">
|
||||
<dialog title = "Execution Results" id = "execution-results-popup">
|
||||
<div class = "action-header">
|
||||
<span class = "icon" role = "icon"></span>
|
||||
|
||||
@@ -110,7 +110,7 @@
|
||||
</dialog>
|
||||
|
||||
<template id = "tplArgumentForm">
|
||||
<dialog>
|
||||
<dialog title = "Arguments" id = "argument-popup">
|
||||
<form class = "action-arguments">
|
||||
<div class = "wrapper">
|
||||
<div class = "action-header">
|
||||
|
||||
@@ -19,6 +19,10 @@ class ActionButton extends window.HTMLElement {
|
||||
this.btn.title = json.title
|
||||
this.btn.onclick = () => {
|
||||
if (json.arguments.length > 0) {
|
||||
for (const oldArgumentForm of document.querySelectorAll('argument-form')) {
|
||||
oldArgumentForm.remove()
|
||||
}
|
||||
|
||||
const frm = document.createElement('argument-form')
|
||||
frm.setup(json, (args) => {
|
||||
this.startAction(args)
|
||||
|
||||
@@ -5,7 +5,7 @@ export class ExecutionDialog {
|
||||
constructFromJson (json) {
|
||||
this.executionUuid = json
|
||||
|
||||
this.dlg = document.querySelector('dialog#executionResults')
|
||||
this.dlg = document.querySelector('dialog#execution-results-popup')
|
||||
|
||||
this.domIcon = this.dlg.querySelector('.icon')
|
||||
this.domTitle = this.dlg.querySelector('.title')
|
||||
|
||||
Reference in New Issue
Block a user