From b3297ccc877b5458f9f6c691d07877a2d450d831 Mon Sep 17 00:00:00 2001 From: Girish Ramakrishnan Date: Fri, 24 Apr 2026 20:04:51 +0200 Subject: [PATCH] test: port to charlie Made-with: Cursor --- test/test.js | 165 ++++++++++++--------------------------------------- 1 file changed, 38 insertions(+), 127 deletions(-) diff --git a/test/test.js b/test/test.js index c45d79b..ec61880 100644 --- a/test/test.js +++ b/test/test.js @@ -1,190 +1,101 @@ #!/usr/bin/env node -import { execSync } from 'node:child_process'; -import assert from 'node:assert/strict'; -import fs from 'node:fs'; -import path from 'node:path'; -import timers from 'node:timers/promises'; -import { Builder, By, until } from 'selenium-webdriver'; -import { Options } from 'selenium-webdriver/chrome'; +import { app, clearCache, click, cloudronCli, goto, password, sendKeys, setupBrowser, takeScreenshot, teardownBrowser, username, waitFor } from '@cloudron/charlie'; -/* global it, xit, describe, before, after, afterEach */ - -if (!process.env.USERNAME || !process.env.PASSWORD) { - console.log('USERNAME and PASSWORD env vars need to be set'); - process.exit(1); -} +/* global it, describe, before, after, afterEach */ describe('Application life cycle test', function () { - this.timeout(0); - - const LOCATION = process.env.LOCATION || 'test'; - const TEST_TIMEOUT = 30000; - const FOLDER = 'xmf'; // keep this small. long folder names fail in automation, not sure why + const FOLDER = 'xmf'; // keep small; long folder names fail in automation const SYNC_PORT = 22001; - const EXEC_ARGS = { cwd: path.resolve(import.meta.dirname, '..'), stdio: 'inherit' }; + const adminUsername = 'admin'; + const adminPassword = 'changeme'; - let browser, app; - const adminUsername = 'admin', adminPassword = 'changeme'; - const username = process.env.USERNAME; - const password = process.env.PASSWORD; - - before(function () { - const chromeOptions = new Options().windowSize({ width: 1280, height: 1024 }); - if (process.env.CI) chromeOptions.addArguments('no-sandbox', 'disable-dev-shm-usage', 'headless'); - browser = new Builder().forBrowser('chrome').setChromeOptions(chromeOptions).build(); - if (!fs.existsSync('./screenshots')) fs.mkdirSync('./screenshots'); - }); - - after(function () { - browser.quit(); - }); + before(setupBrowser); + after(teardownBrowser); afterEach(async function () { - if (!process.env.CI || !app) return; - - const currentUrl = await browser.getCurrentUrl(); - if (!currentUrl.includes(app.domain)) return; - assert.strictEqual(typeof this.currentTest.title, 'string'); - - const screenshotData = await browser.takeScreenshot(); - fs.writeFileSync(`./screenshots/${new Date().getTime()}-${this.currentTest.title.replaceAll(' ', '_')}.png`, screenshotData, 'base64'); + await takeScreenshot(this.currentTest.title); }); - function getAppInfo() { - const inspect = JSON.parse(execSync('cloudron inspect')); - app = inspect.apps.filter(function (a) { return a.location.indexOf(LOCATION) === 0; })[0]; - assert.ok(app && typeof app === 'object'); - } - - async function waitForElement(elem) { - await browser.wait(until.elementLocated(elem), TEST_TIMEOUT); - await browser.wait(until.elementIsVisible(browser.findElement(elem)), TEST_TIMEOUT); - } - - async function login(username, password) { - await browser.manage().deleteAllCookies(); - await browser.get('https://' + app.fqdn); - await waitForElement(By.id('user')); - await browser.findElement(By.id('user')).sendKeys(username); - await browser.findElement(By.id('password')).sendKeys(password); - await browser.findElement(By.xpath('//button[@type="submit"]')).click(); - await waitForElement(By.xpath('//span[text()="Actions"]')); + async function login(uname, pass) { + await clearCache(); + await goto(`https://${app.fqdn}`, 'css=#user'); + await sendKeys('css=#user', uname); + await sendKeys('css=#password', pass); + await click('xpath=//button[@type="submit"]'); + await waitFor('Actions'); } async function logout() { - await browser.get('https://' + app.fqdn); - await waitForElement(By.xpath('//span[text()="Actions"]')); - await browser.findElement(By.xpath('//span[text()="Actions"]')).click(); - await browser.sleep(4000); - await waitForElement(By.xpath('//span[text()="Log Out"]')); - await browser.findElement(By.xpath('//span[text()="Log Out"]')).click(); - await browser.sleep(4000); - await waitForElement(By.id('user')); + await goto(`https://${app.fqdn}`, 'Actions'); + await click('text=Actions'); + await click('text=Log Out'); + await waitFor('css=#user'); } async function loadPage() { - await browser.get('https://' + app.fqdn); - await waitForElement(By.xpath('//span[text()="Actions"]')); + await goto(`https://${app.fqdn}`, 'Actions'); } async function addFolder() { - await browser.get('https://' + app.fqdn); - await browser.findElement(By.css('[ng-click*=addFolder]')).click(); - await browser.sleep(8000); - await waitForElement(By.id('folderPath')); - await browser.sleep(8000); // wait more, not sure why this is needed - await browser.findElement(By.id('folderLabel')).sendKeys(FOLDER); - await browser.findElement(By.id('folderPath')).sendKeys(`/app/data/${FOLDER}`); - await browser.sleep(8000); // without this sometimes only part of the folder name gets through - await browser.findElement(By.css('[ng-click*=saveFolder]')).click(); - await browser.sleep(8000); // without this "stale element" - await waitForElement(By.xpath(`//span[contains(text(), '${FOLDER}')]`)); - await browser.sleep(8000); + await goto(`https://${app.fqdn}`, 'css=[ng-click*=addFolder]'); + await click('css=[ng-click*=addFolder]'); + await waitFor('css=#folderPath'); + await sendKeys('css=#folderLabel', FOLDER); + await sendKeys('css=#folderPath', `/app/data/${FOLDER}`); + await click('css=[ng-click*=saveFolder]'); + await waitFor(FOLDER); } async function checkFolder() { - await browser.get('https://' + app.fqdn); - await browser.sleep(5000); - await browser.get('https://' + app.fqdn); - await browser.wait(until.elementLocated(By.xpath(`//span[text()="${FOLDER}"]`)), TEST_TIMEOUT); + await goto(`https://${app.fqdn}`, FOLDER); } - xit('build app', function () { execSync('cloudron build', EXEC_ARGS); }); - // NO SSO - it('install app (NO SSO)', function () { execSync('cloudron install --no-sso --port-bindings SYNC_PORT=' + SYNC_PORT + ' --location ' + LOCATION, EXEC_ARGS); }); - it('can get app information', getAppInfo); + it('install app (NO SSO)', () => cloudronCli.install({ noSso: true, tcpPortFlags: { SYNC_PORT } })); it('can admin login', login.bind(null, adminUsername, adminPassword)); it('can load page', loadPage); it('can add folder', addFolder); it('can check folder', checkFolder); - it('uninstall app', async function () { - await browser.get('about:blank'); - execSync('cloudron uninstall --app ' + app.id, EXEC_ARGS); - }); + it('uninstall app', cloudronCli.uninstall); // SSO - it('install app (SSO)', function () { execSync('cloudron install --port-bindings SYNC_PORT=' + SYNC_PORT + ' --location ' + LOCATION, EXEC_ARGS); }); - it('can get app information', getAppInfo); + it('install app (SSO)', () => cloudronCli.install({ noSso: false, tcpPortFlags: { SYNC_PORT } })); it('can login', login.bind(null, username, password)); it('can load page', loadPage); it('can add folder', addFolder); it('can logout', logout); - it('backup app', async function () { execSync('cloudron backup create --app ' + app.id, EXEC_ARGS); }); - it('restore app', async function () { - await browser.get('about:blank'); - execSync('cloudron restore --app ' + app.id, EXEC_ARGS); - await timers.setTimeout(5000); - }); + it('backup app', cloudronCli.createBackup); + it('restore app', cloudronCli.restoreFromLatestBackup); it('can login', login.bind(null, username, password)); it('can load page', loadPage); it('can check folder', checkFolder); it('can logout', logout); - it('move to different location', async function () { - await browser.get('about:blank'); - execSync(`cloudron configure --location ${LOCATION}2 --app ${app.id}`, EXEC_ARGS); - await timers.setTimeout(5000); - }); - it('can get app information', getAppInfo); + it('move to different location', cloudronCli.changeLocation); it('can login', login.bind(null, username, password)); it('can load page', loadPage); it('can check folder', checkFolder); it('can logout', logout); - it('uninstall app', async function () { - await browser.get('about:blank'); - execSync('cloudron uninstall --app ' + app.id, EXEC_ARGS); - }); + it('uninstall app', cloudronCli.uninstall); // test update - it('can install app', async function () { - execSync('cloudron install --port-bindings SYNC_PORT=' + SYNC_PORT + ' --appstore-id net.syncthing.cloudronapp2 --location ' + LOCATION, EXEC_ARGS); - await timers.setTimeout(30000); - }); - it('can get app information', getAppInfo); + it('can install app', () => cloudronCli.appstoreInstall({ tcpPortFlags: { SYNC_PORT } })); it('can login', login.bind(null, username, password)); it('can load page', loadPage); it('can add folder', addFolder); it('can logout', logout); - it('can update', async function () { - await browser.get('about:blank'); - execSync('cloudron update --app ' + LOCATION, EXEC_ARGS); - await timers.setTimeout(30000); - }); + it('can update', cloudronCli.update); it('can login', login.bind(null, username, password)); it('can check folder', checkFolder); - it('uninstall app', async function () { - await browser.get('about:blank'); - execSync('cloudron uninstall --app ' + app.id, EXEC_ARGS); - }); + it('uninstall app', cloudronCli.uninstall); });