diff --git a/ui/admin/tests/e2e/helpers/boundary-cli.js b/ui/admin/tests/e2e/helpers/boundary-cli.js index 26d36c6a2..8d7005d8d 100644 --- a/ui/admin/tests/e2e/helpers/boundary-cli.js +++ b/ui/admin/tests/e2e/helpers/boundary-cli.js @@ -4,7 +4,7 @@ */ /* eslint-disable no-undef */ -const { execSync } = require('child_process'); +const { execSync, exec } = require('child_process'); /** * Checks that the boundary cli is available @@ -36,3 +36,44 @@ exports.authenticateBoundaryCli = async () => { console.log(`${e.stderr}`); } }; + +/** + * Uses the boundary CLI to connect to the specified target + * @param {string} targetId ID of the target to be connected to + * @returns ChildProcess representing the result of the command execution + */ +exports.connectToTarget = async (targetId) => { + let connect; + try { + connect = exec( + 'boundary connect' + + ' -target-id=' + + targetId + + ' -exec /usr/bin/ssh --' + + ' -l ' + + process.env.E2E_SSH_USER + + ' -i ' + + process.env.E2E_SSH_KEY_PATH + + ' -o UserKnownHostsFile=/dev/null' + + ' -o StrictHostKeyChecking=no' + + ' -o IdentitiesOnly=yes' + // forces the use of the provided key + ' -p {{boundary.port}}' + + ' {{boundary.ip}}' + ); + } catch (e) { + console.log(`${e.stderr}`); + } + return connect; +}; + +/** + * Uses the boundary CLI to delete the specified organization + * @param {string} orgId ID of the organization to be deleted + */ +exports.deleteOrg = async (orgId) => { + try { + exec('boundary scopes delete -id=' + orgId); + } catch (e) { + console.log(`${e.stderr}`); + } +}; diff --git a/ui/admin/tests/e2e/helpers/boundary-ui.js b/ui/admin/tests/e2e/helpers/boundary-ui.js index aa9cf59bc..14dbc37ab 100644 --- a/ui/admin/tests/e2e/helpers/boundary-ui.js +++ b/ui/admin/tests/e2e/helpers/boundary-ui.js @@ -167,6 +167,36 @@ exports.createNewTarget = async (page) => { return targetName; }; +/** + * Uses the UI to create a new target with address. Assumes you have selected the desired project. + * @param {Page} page Playwright page object + * @returns Name of the target + */ +exports.createNewTargetWithAddress = async (page) => { + const targetName = 'Target ' + nanoid(); + await page + .getByRole('navigation', { name: 'Resources' }) + .getByRole('link', { name: 'Targets' }) + .click(); + await page.getByRole('link', { name: 'New' }).click(); + await page.getByLabel('Name').fill(targetName); + await page.getByLabel('Description').fill('This is an automated test'); + await page.getByLabel('Target Address').fill(process.env.E2E_TARGET_IP); + await page.getByLabel('Default Port').fill(process.env.E2E_SSH_PORT); + await page.getByRole('button', { name: 'Save' }).click(); + await expect( + page.getByRole('alert').getByText('Success', { exact: true }) + ).toBeVisible(); + await page.getByRole('button', { name: 'Dismiss' }).click(); + await expect( + page + .getByRole('navigation', { name: 'breadcrumbs' }) + .getByRole('link', { name: targetName }) + ).toBeVisible(); + + return targetName; +}; + /** * Uses the UI to add a host source to a target. Assume you have selected the desired target. * @param {Page} page Playwright page object @@ -190,3 +220,32 @@ exports.addHostSourceToTarget = async (page, hostSourceName) => { await page.getByRole('button', { name: 'Dismiss' }).click(); await expect(page.getByRole('link', { name: hostSourceName })).toBeVisible(); }; + +/** + * Uses the UI to navigate to Sessions and waits for the session to appear. + * @param {Page} page Playwright page object + * @param {string} targetName Name of the target associated with the session + */ +exports.waitForSessionToBeVisible = async (page, targetName) => { + await page + .getByRole('navigation', { name: 'Resources' }) + .getByRole('link', { name: 'Sessions' }) + .click(); + let i = 0; + let sessionIsVisible = false; + do { + i = i + 1; + sessionIsVisible = await page + .getByRole('cell', { name: targetName }) + .isVisible(); + if (sessionIsVisible) { + break; + } + await page.getByRole('button', { name: 'Refresh' }).click(); + await expect(page.getByRole('button', { name: 'Refresh' })).toBeEnabled(); + } while (i < 5); + + if (!sessionIsVisible) { + throw new Error('Session is not visible'); + } +}; diff --git a/ui/admin/tests/e2e/tests/target.spec.js b/ui/admin/tests/e2e/tests/target.spec.js index cc790df20..abee22033 100644 --- a/ui/admin/tests/e2e/tests/target.spec.js +++ b/ui/admin/tests/e2e/tests/target.spec.js @@ -4,12 +4,14 @@ */ /* eslint-disable no-undef */ -const { test, expect } = require('@playwright/test'); -const { exec, execSync } = require('child_process'); +const { test } = require('@playwright/test'); +const { execSync } = require('child_process'); const { checkEnv, authenticatedState } = require('../helpers/general'); const { authenticateBoundaryCli, checkBoundaryCli, + connectToTarget, + deleteOrg, } = require('../helpers/boundary-cli'); const { createNewOrg, @@ -18,6 +20,8 @@ const { createNewHostSet, createNewHostInHostSet, createNewTarget, + createNewTargetWithAddress, + waitForSessionToBeVisible, addHostSourceToTarget, } = require('../helpers/boundary-ui'); @@ -34,10 +38,12 @@ test.beforeAll(async () => { await checkBoundaryCli(); }); -test('Verify session created then cancel the session', async ({ page }) => { +test('Verify session created to target with host, then cancel the session', async ({ + page, +}) => { await page.goto('/'); - - var connect; + let org; + let connect; try { const orgName = await createNewOrg(page); const projectName = await createNewProject(page); @@ -49,7 +55,7 @@ test('Verify session created then cancel the session', async ({ page }) => { await authenticateBoundaryCli(); const orgs = JSON.parse(execSync('boundary scopes list -format json')); - const org = orgs.items.filter((obj) => obj.name == orgName)[0]; + org = orgs.items.filter((obj) => obj.name == orgName)[0]; const projects = JSON.parse( execSync('boundary scopes list -format json -scope-id ' + org.id) ); @@ -59,50 +65,54 @@ test('Verify session created then cancel the session', async ({ page }) => { ); const target = targets.items.filter((obj) => obj.name == targetName)[0]; - connect = exec( - 'boundary connect' + - ' -target-id=' + - target.id + - ' -exec /usr/bin/ssh --' + - ' -l ' + - process.env.E2E_SSH_USER + - ' -i ' + - process.env.E2E_SSH_KEY_PATH + - ' -o UserKnownHostsFile=/dev/null' + - ' -o StrictHostKeyChecking=no' + - ' -o IdentitiesOnly=yes' + // forces the use of the provided key - ' -p {{boundary.port}}' + - ' {{boundary.ip}}' - ); - - await page - .getByRole('navigation', { name: 'Resources' }) - .getByRole('link', { name: 'Sessions' }) - .click(); - let i = 0; - let sessionIsVisible = false; - do { - i = i + 1; - sessionIsVisible = await page - .getByRole('cell', { name: targetName }) - .isVisible(); - if (sessionIsVisible) { - break; - } - await page.getByRole('button', { name: 'Refresh' }).click(); - await expect(page.getByRole('button', { name: 'Refresh' })).toBeEnabled(); - } while (i < 5); - - if (!sessionIsVisible) { - throw new Error('Session is not visible'); - } - + connect = await connectToTarget(target.id); + await waitForSessionToBeVisible(page, targetName); await page .getByRole('cell', { name: targetName }) .locator('..') .getByRole('button', { name: 'Cancel' }) .click(); } finally { + await deleteOrg(org.id); + // End `boundary connect` process + if (connect) { + connect.kill('SIGTERM'); + } + } +}); + +test('Verify session created to target with address, then cancel the session', async ({ + page, +}) => { + await page.goto('/'); + let org; + let connect; + try { + const orgName = await createNewOrg(page); + const projectName = await createNewProject(page); + const targetName = await createNewTargetWithAddress(page); + + await authenticateBoundaryCli(); + const orgs = JSON.parse(execSync('boundary scopes list -format json')); + org = orgs.items.filter((obj) => obj.name == orgName)[0]; + const projects = JSON.parse( + execSync('boundary scopes list -format json -scope-id ' + org.id) + ); + const project = projects.items.filter((obj) => obj.name == projectName)[0]; + const targets = JSON.parse( + execSync('boundary targets list -format json -scope-id ' + project.id) + ); + const target = targets.items.filter((obj) => obj.name == targetName)[0]; + + connect = await connectToTarget(target.id); + await waitForSessionToBeVisible(page, targetName); + await page + .getByRole('cell', { name: targetName }) + .locator('..') + .getByRole('button', { name: 'Cancel' }) + .click(); + } finally { + await deleteOrg(org.id); // End `boundary connect` process if (connect) { connect.kill('SIGTERM');