diff --git a/test/test.js b/test/test.js index 0ead062..8dcabae 100755 --- a/test/test.js +++ b/test/test.js @@ -1,138 +1,56 @@ #!/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 { Builder, By, until } from 'selenium-webdriver'; -import { Options } from 'selenium-webdriver/chrome'; +import { app, click, cloudronCli, goto, loginOIDC, sendKeys, setupBrowser, takeScreenshot, teardownBrowser, waitFor } from '@cloudron/charlie'; -/* global describe */ -/* global before */ -/* global after */ -/* global afterEach */ -/* global it */ -/* global xit */ - -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 POST_LOGIN_LOCATOR = 'xpath=//span[contains(., "Bookmarks")] | //strong[text()="Successfully authenticated from Cloudron account."]'; - const LOCATION = process.env.LOCATION || 'test'; - const TEST_TIMEOUT = parseInt(process.env.TIMEOUT) || 10000; - const EXEC_ARGS = { cwd: path.resolve(import.meta.dirname, '..'), stdio: 'inherit' }; - - let browser, app; - 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); }); - async function waitForElement(elem) { - await browser.wait(until.elementLocated(elem), TEST_TIMEOUT); - await browser.wait(until.elementIsVisible(browser.findElement(elem)), TEST_TIMEOUT); + async function checkRegistrationClosed() { + const createBtn = 'xpath=//div[@class="sign-in-banner"]/descendant::button/span[contains(text(), "Create account")] | //div[@class="sign-in-banner"]/descendant::a/span[contains(text(), "Create account")]'; + await goto(`https://${app.fqdn}`, createBtn); + await click(createBtn); + await waitFor('is currently not possible'); } - async function exists(selector) { - await browser.wait(until.elementLocated(selector), TEST_TIMEOUT); + async function login(addr, pass) { + await goto(`https://${app.fqdn}/auth/sign_in`, 'text=Log in'); + await sendKeys('css=#user_email', addr); + await sendKeys('css=#user_password', pass); + await click('text=Log in'); } - async function visible(selector) { - await exists(selector); - await browser.wait(until.elementIsVisible(browser.findElement(selector)), TEST_TIMEOUT); - } - - async function checkRegistration(mode) { - if (mode === 'none') { - await browser.get('https://' + app.fqdn); - await browser.sleep(2000); - await browser.findElement(By.xpath('//div[@class="sign-in-banner"]/descendant::button/span[contains(text(), "Create account")] | //div[@class="sign-in-banner"]/descendant::a/span[contains(text(), "Create account")]')).click(); - await visible(By.xpath('//span[contains(text()[2], "is currently not possible")]')); - } else if (mode === 'open') { - await browser.get('https://' + app.fqdn + '/auth/sign_up'); - await visible(By.xpath('//button[contains(text(), "Sign up")]')); - } - } - - async function login(username, password) { - await browser.get('https://' + app.fqdn + '/auth/sign_in'); // there is also separate login page at /users/sign_in - await visible(By.xpath('//button[contains(text(), "Log in")]')); - await browser.findElement(By.id('user_email')).sendKeys(username); - await browser.findElement(By.id('user_password')).sendKeys(password); - await browser.findElement(By.xpath('//button[contains(text(), "Log in")]')).click(); - await browser.sleep(3000); // can be wizard or timeline at this point - } - - async function loginOIDC(username, password, hasSession) { - browser.manage().deleteAllCookies(); - await browser.get(`https://${app.fqdn}/auth/sign_in`); - await browser.sleep(4000); - - await browser.findElement(By.xpath('//a[contains(@class, "button") and text()="Cloudron"]')).click(); - await browser.sleep(4000); - - if (!hasSession) { - await waitForElement(By.id('inputUsername')); - await browser.findElement(By.id('inputUsername')).sendKeys(username); - await browser.findElement(By.id('inputPassword')).sendKeys(password); - await browser.findElement(By.id('loginSubmitButton')).click(); - } - - await waitForElement(By.xpath('//span[contains(., "Bookmarks")] | //strong[text()="Successfully authenticated from Cloudron account."]')); + async function loginOidc() { + await goto(`https://${app.fqdn}/auth/sign_in`, 'xpath=//a[contains(@class, "button") and text()="Cloudron"]'); + await click('xpath=//a[contains(@class, "button") and text()="Cloudron"]'); + await loginOIDC(POST_LOGIN_LOCATOR); } async function logout() { - await browser.get('https://' + app.fqdn + '/settings/preferences/appearance'); // there is also separate login page at /users/sign_in - await browser.wait(until.elementLocated(By.id('logout')), TEST_TIMEOUT); - await browser.findElement(By.id('logout')).click(); - await visible(By.id('user_email')); + await goto(`https://${app.fqdn}/settings/preferences/appearance`, 'css=#logout'); + await click('css=#logout'); + await waitFor('css=#user_email'); } async function checkTimeline() { - await browser.get('https://' + app.fqdn + '/home'); - await visible(By.xpath('//span[contains(text(), "Your home timeline is empty")]')); + await goto(`https://${app.fqdn}/home`, 'Your home timeline is empty'); } - function getAppInfo() { - const inspect = JSON.parse(execSync('cloudron inspect')); - app = inspect.apps.filter(function (a) { return a.location === LOCATION || a.location === LOCATION + '2'; })[0]; - assert.ok(app && typeof app === 'object'); - } - - xit('build app', function () { execSync('cloudron build', EXEC_ARGS); }); - // No SSO - it('install app (no sso)', function () { execSync('cloudron install --no-sso --location ' + LOCATION, EXEC_ARGS); }); - it('can get app information', getAppInfo); + it('install app (no sso)', function () { cloudronCli.install({ noSso: true }); }); - it('has registration open', checkRegistration.bind(null, 'none')); + it('has registration closed', checkRegistrationClosed); let testPassword; it('create a user with CLI', function () { - const output = execSync('cloudron exec --app ' + LOCATION + ' -- bin/tootctl accounts create test --email=test@cloudron.io', { cwd: path.resolve(import.meta.dirname, '..'), encoding: 'utf8' }); + const output = cloudronCli.exec('bin/tootctl accounts create test --email=test@cloudron.io').toString(); console.log(output); testPassword = output.slice(output.indexOf('New password: ') + 'New password: '.length).trim(); console.log(testPassword); @@ -143,64 +61,43 @@ describe('Application life cycle test', function () { }); it('shows confirmation page', async function () { - await waitForElement(By.xpath('//a[@href="/auth/confirmation/new"] | //form[@id="edit_user_1"]')); + await waitFor('xpath=//a[@href="/auth/confirmation/new"] | //form[@id="edit_user_1"]'); }); - it('uninstall app (no sso)', async function () { - await browser.get('about:blank'); - execSync('cloudron uninstall --app ' + app.id, EXEC_ARGS); - }); + it('uninstall app (no sso)', cloudronCli.uninstall); // SSO - it('install app (sso)', function () { execSync('cloudron install --location ' + LOCATION, EXEC_ARGS); }); + it('install app (sso)', cloudronCli.install); - it('can get app information', getAppInfo); - it('registration is disabled', checkRegistration.bind(null, 'none')); - it('can OIDC login', loginOIDC.bind(null, username, password, false)); + it('registration is disabled', checkRegistrationClosed); + it('can OIDC login', loginOidc); it('can see timeline', checkTimeline); it('can logout', logout); - it('backup app', function () { execSync('cloudron backup create --app ' + app.id, EXEC_ARGS); }); - it('restore app', function () { - const backups = JSON.parse(execSync('cloudron backup list --raw')); - execSync('cloudron uninstall --app ' + app.id, EXEC_ARGS); - execSync('cloudron install --location ' + LOCATION, EXEC_ARGS); - getAppInfo(); - execSync(`cloudron restore --backup ${backups[0].id} --app ${app.id}`, EXEC_ARGS); - }); + it('backup app', cloudronCli.createBackup); + it('restore app', cloudronCli.restoreFromLatestBackup); - it('can OIDC login', loginOIDC.bind(null, username, password, true)); + it('can OIDC login', loginOidc); it('can see timeline', checkTimeline); - it('can restart app', function () { execSync('cloudron restart --app ' + app.id, EXEC_ARGS); }); + it('can restart app', cloudronCli.restart); it('can see timeline', checkTimeline); - it('move to different location', async function () { - await browser.get('about:blank'); - execSync('cloudron configure --location ' + LOCATION + '2 --app ' + app.id, EXEC_ARGS); - }); - it('can get app information', getAppInfo); + it('move to different location', cloudronCli.changeLocation); - it('can OIDC login', loginOIDC.bind(null, username, password, true)); + it('can OIDC login', loginOidc); it('can see timeline', checkTimeline); - 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', function () { execSync('cloudron install --appstore-id org.joinmastodon.cloudronapp --location ' + LOCATION, EXEC_ARGS); }); - it('can get app information', getAppInfo); - it('can OIDC login', loginOIDC.bind(null, username, password, true)); + it('can install app', cloudronCli.appstoreInstall); + it('can OIDC login', loginOidc); it('can logout', logout); - it('can update', async function () { - await browser.get('about:blank'); - execSync('cloudron update --app ' + LOCATION, EXEC_ARGS); - }); + it('can update', cloudronCli.update); - it('can OIDC login', loginOIDC.bind(null, username, password, true)); + it('can OIDC login', loginOidc); - it('uninstall app', function () { execSync('cloudron uninstall --app ' + app.id, EXEC_ARGS); }); + it('uninstall app', cloudronCli.uninstall); });