From be6b68ddc31f0a34c7def633d4314a80682d47b9 Mon Sep 17 00:00:00 2001 From: GermanBluefox Date: Wed, 25 Oct 2023 12:35:29 +0200 Subject: [PATCH] Implement admin GUI testing --- package.json | 2 + test/engineHelper.js | 104 ++++++++++++++++++++++++++++++++++++++++ test/guiHelper.js | 66 +++++++++++++++++++++++++ test/testAdapter.gui.js | 28 +++++++++++ 4 files changed, 200 insertions(+) create mode 100644 test/engineHelper.js create mode 100644 test/guiHelper.js create mode 100644 test/testAdapter.gui.js diff --git a/package.json b/package.json index 1a787a8..1331812 100644 --- a/package.json +++ b/package.json @@ -30,9 +30,11 @@ "@alcalzone/release-script": "^3.6.0", "@alcalzone/release-script-plugin-iobroker": "^3.6.0", "@alcalzone/release-script-plugin-license": "^3.5.9", + "@iobroker/legacy-testing": "^1.0.1", "@types/iobroker": "^5.0.6", "@types/jsonwebtoken": "^9.0.4", "@types/node": "^20.8.8", + "puppeteer": "^21.4.1", "@typescript-eslint/eslint-plugin": "^6.9.0", "chai": "^4.3.10", "eslint": "^8.52.0", diff --git a/test/engineHelper.js b/test/engineHelper.js new file mode 100644 index 0000000..57c7ca4 --- /dev/null +++ b/test/engineHelper.js @@ -0,0 +1,104 @@ +const fs = require('fs'); +const setup = require('@iobroker/legacy-testing'); + +let rootDir = `${__dirname}/../../../`; +let objects = null; +let states = null; +let onStateChanged = null; + +function deleteFoldersRecursive(path) { + if (path.endsWith('/')) { + path = path.substring(0, path.length - 1); + } + if (fs.existsSync(path)) { + const files = fs.readdirSync(path); + for (const file of files) { + const curPath = `${path}/${file}`; + const stat = fs.statSync(curPath); + if (stat.isDirectory()) { + deleteFoldersRecursive(curPath); + fs.rmdirSync(curPath); + } else { + fs.unlinkSync(curPath); + } + } + } +} + +function startIoBroker(adapterName, options) { + options = options || {}; + if (options.rootDir) { + rootDir = options.rootDir; + } + + return new Promise(async resolve => { + // delete the old project + deleteFoldersRecursive(`${rootDir}tmp/screenshots`); + + await setup.setOfflineState(`system.adapter.${adapterName}.0.alive`, { val: false }); + + setup.setupController(null, async systemConfig => { + // disable statistics and set license accepted + systemConfig.common.licenseConfirmed = true; + systemConfig.common.diag = 'none'; + await setup.setObject('system.config', systemConfig); + + // lets the web adapter start on port 18080 + const config = await setup.getAdapterConfig(0, adapterName); + if (config && config.common) { + config.common.enabled = true; + await setup.setAdapterConfig(config.common, config.native, 0, adapterName); + } + + setup.startController( + false, // do not start widgets + (/* id, obj */) => {}, + (id, state) => onStateChanged && onStateChanged(id, state), + async (_objects, _states) => { + objects = _objects; + states = _states; + setup.startCustomAdapter(adapterName, 0); + await checkIsWelcomeStartedAsync(states); + resolve({ objects, states }); + }); + }); + }); +} + +async function stopIoBroker(adapterName) { + await setup.stopCustomAdapter(adapterName, 0); + + await new Promise(resolve => + setup.stopController(normalTerminated => { + console.log(`Adapter normal terminated: ${normalTerminated}`); + resolve(); + })); +} + +function checkIsWelcomeStarted(adapterName, states, cb, counter) { + counter = counter === undefined ? 20 : counter; + if (counter === 0) { + return cb && cb(`Cannot check value Of State system.adapter.${adapterName}.0.alive`); + } + + states.getState(`system.adapter.${adapterName}.0.alive`, (err, state) => { + console.log(`[${counter}]Check if ${adapterName} is started "system.adapter.${adapterName}.0.alive" = ${JSON.stringify(state)}`); + err && console.error(err); + if (state && state.val) { + cb && cb(); + } else { + setTimeout(() => + checkIsWelcomeStarted(states, cb, counter - 1), 500); + } + }); +} + +function checkIsWelcomeStartedAsync(adapterName, states, counter) { + return new Promise(resolve => checkIsWelcomeStarted(adapterName, states, resolve, counter)); +} + +module.exports = { + startIoBroker, + stopIoBroker, + setOnStateChanged: cb => onStateChanged = cb +}; \ No newline at end of file diff --git a/test/guiHelper.js b/test/guiHelper.js new file mode 100644 index 0000000..a710231 --- /dev/null +++ b/test/guiHelper.js @@ -0,0 +1,66 @@ +const puppeteer = require('puppeteer'); +const { blue, cyan, red, yellow } = require('colorette'); +const fs = require('fs'); + +const rootDir = `${__dirname}/../`; +let gBrowser; +let gPage; + +async function startBrowser(adapterName, headless) { + const browser = await puppeteer.launch({ + headless: headless === undefined ? false : headless, + args: ['--no-sandbox', '--disable-setuid-sandbox'] + }); + const pages = await browser.pages(); + const timeout = 5000; + pages[0].setDefaultTimeout(timeout); + + await pages[0].setViewport( { + width: 1920, + height: 1080, + deviceScaleFactor: 1 + }); + + gBrowser = browser; + gPage = pages[0]; + + // LOGGING + gPage + .on('console', message => { + const type = message.type().substr(0, 3).toUpperCase(); + const colors = { + LOG: text => text, + ERR: red, + WAR: yellow, + INF: cyan + }; + + const color = colors[type] || blue; + console.log(color(`[BROWSER] ${type} ${message.text()}`)); + }) + .on('pageerror', ({ message }) => console.log(red(`[BROWSER] ${message}`))); + + await gPage.goto(`http://127.0.0.1:8081/#tab-instances/config/system.adapter.${adapterName}.0`, { waitUntil: 'domcontentloaded' }); + + // Create directory + !fs.existsSync(`${rootDir}tmp/screenshots`) && fs.mkdirSync(`${rootDir}tmp/screenshots`); + await gPage.screenshot({ path: `${rootDir}tmp/screenshots/00_starting.png` }); + + return { browser, page: pages[0] }; +} + +async function stopBrowser(browser) { + browser = browser || gBrowser; + await browser.close(); +} + +async function screenshot(page, fileName) { + page = page || gPage; + await page.screenshot({ path: `${rootDir}tmp/screenshots/${fileName}.png` }); +} + +module.exports = { + startBrowser, + stopBrowser, + screenshot +}; \ No newline at end of file diff --git a/test/testAdapter.gui.js b/test/testAdapter.gui.js new file mode 100644 index 0000000..b66fedd --- /dev/null +++ b/test/testAdapter.gui.js @@ -0,0 +1,28 @@ +const engineHelper = require('./engineHelper'); +const guiHelper = require('./guiHelper'); +const adapterName = require('../package.json').name.replace('iobroker.', ''); +let gPage; + +describe('test-admin-gui', () => { + before(async function (){ + this.timeout(240_000); + + // install js-controller, web and vis-2-beta + await engineHelper.startIoBroker(adapterName); + const { page } = await guiHelper.startBrowser(adapterName, process.env.CI === 'true'); + gPage = page; + }); + + it('Check web server', async function (){ + this.timeout(5_000); + await gPage.waitForSelector('.MuiAvatar-root', { timeout: 5_000 }); + }); + + after(async function () { + this.timeout(5000); + await guiHelper.stopBrowser(); + console.log('BROWSER stopped'); + await engineHelper.stopIoBroker(); + console.log('ioBroker stopped'); + }); +}); \ No newline at end of file