diff --git a/ui/admin/app/templates/scopes/scope/session-recordings/session-recording/channels-by-connection/index.hbs b/ui/admin/app/templates/scopes/scope/session-recordings/session-recording/channels-by-connection/index.hbs index 33b996f49..46754b88a 100644 --- a/ui/admin/app/templates/scopes/scope/session-recordings/session-recording/channels-by-connection/index.hbs +++ b/ui/admin/app/templates/scopes/scope/session-recordings/session-recording/channels-by-connection/index.hbs @@ -21,12 +21,8 @@ {{#if (can 'reapplyStoragePolicy session-recording' @model.sessionRecording) }} - - + + diff --git a/ui/admin/tests/acceptance/session-recordings/list-test.js b/ui/admin/tests/acceptance/session-recordings/list-test.js index 00e09f5ef..d64291d45 100644 --- a/ui/admin/tests/acceptance/session-recordings/list-test.js +++ b/ui/admin/tests/acceptance/session-recordings/list-test.js @@ -4,20 +4,14 @@ */ import { module, test } from 'qunit'; -import { - visit, - currentURL, - click, - fillIn, - waitUntil, - findAll, -} from '@ember/test-helpers'; +import { visit, currentURL, click, fillIn, waitFor } from '@ember/test-helpers'; import { setupApplicationTest } from 'admin/tests/helpers'; import setupMirage from 'ember-cli-mirage/test-support/setup-mirage'; import { setupIndexedDb } from 'api/test-support/helpers/indexed-db'; import { authenticateSession } from 'ember-simple-auth/test-support'; -import * as commonSelectors from 'admin/tests/helpers/selectors'; import { faker } from '@faker-js/faker'; +import * as commonSelectors from 'admin/tests/helpers/selectors'; +import * as selectors from './selectors'; module('Acceptance | session recordings | list', function (hooks) { setupApplicationTest(hooks); @@ -26,17 +20,6 @@ module('Acceptance | session recordings | list', function (hooks) { let featuresService; - // Selectors - const SESSION_RECORDING_TITLE = 'Session Recordings'; - const SEARCH_INPUT_SELECTOR = '.search-filtering [type="search"]'; - const NO_RESULTS_MSG_SELECTOR = '[data-test-no-session-recording-results]'; - const FILTER_TOGGLE_SELECTOR = (name) => - `[data-test-session-recordings-bar] div[name="${name}"] button`; - const FILTER_APPLY_BUTTON = (name) => - `[data-test-session-recordings-bar] div[name="${name}"] div div:last-child button[type="button"]`; - const LAST_3_DAYS_OPTION = - '[data-test-session-recordings-bar] div[name="time"] li:nth-child(2) button'; - // Instances const instances = { scopes: { @@ -127,19 +110,20 @@ module('Acceptance | session recordings | list', function (hooks) { test('users can navigate to session-recordings with proper authorization', async function (assert) { await visit(urls.globalScope); - assert.true( instances.scopes.global.authorized_collection_actions[ 'session-recordings' ].includes('list'), ); - assert.dom(`[href="${urls.sessionRecordings}"]`).exists(); + + assert.dom(commonSelectors.HREF(urls.sessionRecordings)).isVisible(); assert .dom(commonSelectors.SIDEBAR_NAV_CONTENT) - .includesText(SESSION_RECORDING_TITLE); + .includesText(selectors.SESSION_RECORDING_TITLE); // Visit session recordings - await click(`[href="${urls.sessionRecordings}"]`); + await click(commonSelectors.HREF(urls.sessionRecordings)); + assert.strictEqual(currentURL(), urls.sessionRecordings); }); @@ -147,7 +131,6 @@ module('Acceptance | session recordings | list', function (hooks) { instances.scopes.global.authorized_collection_actions[ 'session-recordings' ] = []; - await visit(urls.globalScope); assert.false( @@ -157,83 +140,95 @@ module('Acceptance | session recordings | list', function (hooks) { ); assert .dom(commonSelectors.SIDEBAR_NAV_CONTENT) - .doesNotIncludeText(SESSION_RECORDING_TITLE); - assert.dom(`[href="${urls.sessionRecordings}"]`).doesNotExist(); + .doesNotIncludeText(selectors.SESSION_RECORDING_TITLE); + assert.dom(commonSelectors.HREF(urls.sessionRecordings)).doesNotExist(); }); test('user can search for a session recording by id', async function (assert) { - await visit(urls.sessionRecordings); + await visit(urls.globalScope); - assert.dom(commonSelectors.HREF(urls.sessionRecording)).exists(); - assert.dom(commonSelectors.HREF(urls.sessionRecording2)).exists(); + await click(commonSelectors.HREF(urls.sessionRecordings)); - await fillIn(SEARCH_INPUT_SELECTOR, instances.sessionRecording.id); - await waitUntil( - () => findAll(commonSelectors.HREF(urls.sessionRecording2)).length === 0, - ); + assert.dom(commonSelectors.HREF(urls.sessionRecording)).isVisible(); + assert.dom(commonSelectors.HREF(urls.sessionRecording2)).isVisible(); - assert.dom(commonSelectors.HREF(urls.sessionRecording)).exists(); + await fillIn(commonSelectors.SEARCH_INPUT, instances.sessionRecording.id); + await waitFor(commonSelectors.HREF(urls.sessionRecording2), { count: 0 }); + + assert.dom(commonSelectors.HREF(urls.sessionRecording)).isVisible(); assert.dom(commonSelectors.HREF(urls.sessionRecording2)).doesNotExist(); }); test('user can search for a session recording by id and get no results', async function (assert) { - await visit(urls.sessionRecordings); + await visit(urls.globalScope); - assert.dom(commonSelectors.HREF(urls.sessionRecording)).exists(); - assert.dom(commonSelectors.HREF(urls.sessionRecording2)).exists(); + await click(commonSelectors.HREF(urls.sessionRecordings)); - await fillIn(SEARCH_INPUT_SELECTOR, 'sr_404'); - await waitUntil(() => findAll(NO_RESULTS_MSG_SELECTOR).length === 1); + assert.dom(commonSelectors.HREF(urls.sessionRecording)).isVisible(); + assert.dom(commonSelectors.HREF(urls.sessionRecording2)).isVisible(); + + await fillIn(commonSelectors.SEARCH_INPUT, 'sr_404'); + await waitFor(selectors.NO_RESULTS_MSG, { count: 1 }); assert.dom(commonSelectors.HREF(urls.sessionRecording)).doesNotExist(); assert.dom(commonSelectors.HREF(urls.sessionRecording2)).doesNotExist(); - assert.dom(NO_RESULTS_MSG_SELECTOR).includesText('No results found'); + assert.dom(selectors.NO_RESULTS_MSG).includesText('No results found'); }); test('user can filter session recordings by user', async function (assert) { - await visit(urls.sessionRecordings); + await visit(urls.globalScope); - assert.dom('tbody tr').exists({ count: 2 }); + await click(commonSelectors.HREF(urls.sessionRecordings)); - await click(FILTER_TOGGLE_SELECTOR('user')); - await click(`input[value="${instances.user.id}"]`); - await click(FILTER_APPLY_BUTTON('user')); + assert.dom(commonSelectors.TABLE_ROW).isVisible({ count: 2 }); - assert.dom('tbody tr').exists({ count: 1 }); + await click(commonSelectors.FILTER_DROPDOWN('user')); + await click(commonSelectors.FILTER_DROPDOWN_ITEM(instances.user.id)); + await click(commonSelectors.FILTER_DROPDOWN_ITEM_APPLY_BTN('user')); + + assert.dom(commonSelectors.TABLE_ROW).isVisible({ count: 1 }); }); test('user can filter session recordings by scope', async function (assert) { - await visit(urls.sessionRecordings); + await visit(urls.globalScope); - assert.dom('tbody tr').exists({ count: 2 }); + await click(commonSelectors.HREF(urls.sessionRecordings)); - await click(FILTER_TOGGLE_SELECTOR('target')); - await click(`input[value="${instances.target.id}"]`); - await click(FILTER_APPLY_BUTTON('target')); + assert.dom(commonSelectors.TABLE_ROW).isVisible({ count: 2 }); - assert.dom('tbody tr').exists({ count: 1 }); + await click(commonSelectors.FILTER_DROPDOWN('target')); + await click(commonSelectors.FILTER_DROPDOWN_ITEM(instances.target.id)); + await click(commonSelectors.FILTER_DROPDOWN_ITEM_APPLY_BTN('target')); + + assert.dom(commonSelectors.TABLE_ROW).isVisible({ count: 1 }); }); test('user can filter session recordings by target', async function (assert) { - await visit(urls.sessionRecordings); + await visit(urls.globalScope); - assert.dom('tbody tr').exists({ count: 2 }); + await click(commonSelectors.HREF(urls.sessionRecordings)); - await click(FILTER_TOGGLE_SELECTOR('scope')); - await click(`input[value="${instances.target.scope.id}"]`); - await click(FILTER_APPLY_BUTTON('scope')); + assert.dom(commonSelectors.TABLE_ROW).isVisible({ count: 2 }); - assert.dom('tbody tr').exists({ count: 1 }); + await click(commonSelectors.FILTER_DROPDOWN('scope')); + await click( + commonSelectors.FILTER_DROPDOWN_ITEM(instances.target.scope.id), + ); + await click(commonSelectors.FILTER_DROPDOWN_ITEM_APPLY_BTN('scope')); + + assert.dom(commonSelectors.TABLE_ROW).isVisible({ count: 1 }); }); test('user can filter session recordings by time', async function (assert) { - await visit(urls.sessionRecordings); + await visit(urls.globalScope); - assert.dom('tbody tr').exists({ count: 2 }); + await click(commonSelectors.HREF(urls.sessionRecordings)); - await click(FILTER_TOGGLE_SELECTOR('time')); - await click(LAST_3_DAYS_OPTION); + assert.dom(commonSelectors.TABLE_ROW).isVisible({ count: 2 }); - assert.dom('tbody tr').exists({ count: 1 }); + await click(commonSelectors.FILTER_DROPDOWN('time')); + await click(selectors.LAST_3_DAYS_OPTION); + + assert.dom(commonSelectors.TABLE_ROW).isVisible({ count: 1 }); }); }); diff --git a/ui/admin/tests/acceptance/session-recordings/read-test.js b/ui/admin/tests/acceptance/session-recordings/read-test.js index 1613f69a7..2139723b0 100644 --- a/ui/admin/tests/acceptance/session-recordings/read-test.js +++ b/ui/admin/tests/acceptance/session-recordings/read-test.js @@ -10,6 +10,8 @@ import setupMirage from 'ember-cli-mirage/test-support/setup-mirage'; import a11yAudit from 'ember-a11y-testing/test-support/audit'; import { authenticateSession } from 'ember-simple-auth/test-support'; import { setupIndexedDb } from 'api/test-support/helpers/indexed-db'; +import * as commonSelectors from 'admin/tests/helpers/selectors'; +import * as selectors from './selectors'; module('Acceptance | session-recordings | read', function (hooks) { setupApplicationTest(hooks); @@ -18,12 +20,6 @@ module('Acceptance | session-recordings | read', function (hooks) { let featuresService; - // Selectors - const LIST_SESSION_RECORDING_BUTTON = - 'table > tbody > tr > td:last-child > a'; - const LIST_CHANNEL_RECORDING_BUTTON = - 'table > tbody > tr > td:last-child > a'; - // Instances const instances = { scopes: { @@ -81,23 +77,31 @@ module('Acceptance | session-recordings | read', function (hooks) { const session = this.owner.lookup('service:session'); session.set('data.theme', 'light'); featuresService.enable('ssh-session-recording'); + await visit(urls.globalScope); + // Visit session recordings - await visit(urls.sessionRecordings); + await click(commonSelectors.HREF(urls.sessionRecordings)); await a11yAudit(); // Click a session recording and check it navigates properly - await click(LIST_SESSION_RECORDING_BUTTON); + await click(selectors.TABLE_FIRST_ROW_ACTION_LINK); await a11yAudit(); + assert.strictEqual(currentURL(), urls.sessionRecording); }); test('user can navigate to a session recording with proper authorization', async function (assert) { featuresService.enable('ssh-session-recording'); + await visit(urls.globalScope); + // Visit session recordings - await visit(urls.sessionRecordings); + await click(commonSelectors.HREF(urls.sessionRecordings)); + assert.true(instances.sessionRecording.authorized_actions.includes('read')); - assert.dom(`[href="${urls.sessionRecording}"]`).exists(); + assert.dom(commonSelectors.HREF(urls.sessionRecording)).isVisible(); + // Click a session recording and check it navigates properly - await click(LIST_SESSION_RECORDING_BUTTON); + await click(selectors.TABLE_FIRST_ROW_ACTION_LINK); + assert.strictEqual(currentURL(), urls.sessionRecording); }); @@ -107,22 +111,27 @@ module('Acceptance | session-recordings | read', function (hooks) { instances.sessionRecording.authorized_actions.filter( (item) => item !== 'read', ); + await visit(urls.globalScope); + // Visit session recordings - await visit(urls.sessionRecordings); - assert.dom(LIST_SESSION_RECORDING_BUTTON).doesNotExist(); + await click(commonSelectors.HREF(urls.sessionRecordings)); + + assert.dom(selectors.TABLE_FIRST_ROW_ACTION_LINK).doesNotExist(); }); test('user can navigate to a channel recording', async function (assert) { featuresService.enable('ssh-session-recording'); - // Visit session recording - await visit(urls.sessionRecording); - assert.dom('table').hasClass('hds-table'); + await visit(urls.sessionRecordings); + + // Visit session recordings + await click(commonSelectors.HREF(urls.sessionRecording)); // Click a channel recording and check it navigates properly - await click(LIST_CHANNEL_RECORDING_BUTTON); + await click(selectors.TABLE_FIRST_ROW_ACTION_LINK); + assert.strictEqual(currentURL(), urls.channelRecording); }); - test('users can navigate to session recording and incorrect url autocorrects', async function (assert) { + test('users can navigate to session recording and incorrect url auto-corrects', async function (assert) { featuresService.enable('ssh-session-recording'); const incorrectUrl = `/scopes/${instances.scopes.org.id}/session-recordings/${instances.sessionRecording.id}/channels-by-connection`; diff --git a/ui/admin/tests/acceptance/session-recordings/selectors.js b/ui/admin/tests/acceptance/session-recordings/selectors.js new file mode 100644 index 000000000..bc28c3cdd --- /dev/null +++ b/ui/admin/tests/acceptance/session-recordings/selectors.js @@ -0,0 +1,24 @@ +/** + * Copyright (c) HashiCorp, Inc. + * SPDX-License-Identifier: BUSL-1.1 + */ + +export const SESSION_RECORDING_TITLE = 'Session Recordings'; +export const TABLE_FIRST_ROW_ACTION_LINK = + 'tbody tr:first-child td:last-child a'; +export const SESSION_RECORDING_PLAYER = '.session-recording-player'; +export const SESSION_RECORDING_PLAYER_LINK = + '.session-recording-player-header > a'; +export const RETAIN_UNTIL_OPTION = '[data-test-retain-until]'; +export const DELETE_AFTER_OPTION = '[data-test-delete-after]'; + +// Search and filtering selectors +export const NO_RESULTS_MSG = '[data-test-no-session-recording-results]'; +export const LAST_3_DAYS_OPTION = + '[data-test-session-recordings-bar] div[name="time"] li:nth-child(2) button'; + +// Manage actions dropdown selectors +export const MANAGE_DROPDOWN = + '[data-test-manage-channels-dropdown] button:first-child'; +export const DELETE_ACTION = + '[data-test-manage-channels-dropdown] ul li:last-child button'; diff --git a/ui/admin/tests/acceptance/session-recordings/session-recording/channels-by-connection/channel-test.js b/ui/admin/tests/acceptance/session-recordings/session-recording/channels-by-connection/channel-test.js index 9dcc24934..0346fe80f 100644 --- a/ui/admin/tests/acceptance/session-recordings/session-recording/channels-by-connection/channel-test.js +++ b/ui/admin/tests/acceptance/session-recordings/session-recording/channels-by-connection/channel-test.js @@ -7,10 +7,12 @@ import { module, test } from 'qunit'; import { visit, currentURL, click } from '@ember/test-helpers'; import { setupApplicationTest } from 'admin/tests/helpers'; import setupMirage from 'ember-cli-mirage/test-support/setup-mirage'; +import a11yAudit from 'ember-a11y-testing/test-support/audit'; import { authenticateSession } from 'ember-simple-auth/test-support'; import { Response } from 'miragejs'; import { setupIndexedDb } from 'api/test-support/helpers/indexed-db'; import * as commonSelectors from 'admin/tests/helpers/selectors'; +import * as selectors from '../../selectors'; module( 'Acceptance | session-recordings | session-recording | channels-by-connection | channel', @@ -22,9 +24,6 @@ module( let featuresService; let getRecordingCount; - const DELETE_DROPDOWN_SELECTOR = '[data-test-manage-dropdown-delete]'; - const DROPDOWN_SELECTOR = '[data-test-manage-dropdown]'; - // Instances const instances = { scopes: { @@ -84,27 +83,34 @@ module( }); test('user can navigate to a channel', async function (assert) { + await visit(urls.sessionRecording); + // Visit channel - await visit(urls.channelRecording); + await click(commonSelectors.HREF(urls.channelRecording)); + await a11yAudit(); + assert.strictEqual(currentURL(), urls.channelRecording); }); test('user can view recording with proper authorization', async function (assert) { + await visit(urls.sessionRecording); + // Visit channel - await visit(urls.channelRecording); + await click(commonSelectors.HREF(urls.channelRecording)); // if authorized player will render - assert.dom('.session-recording-player').exists(); + assert.dom(selectors.SESSION_RECORDING_PLAYER).isVisible(); }); test('user cannot view recording without proper authorization: channel mime_types', async function (assert) { instances.channelRecording.mime_types = []; + await visit(urls.sessionRecording); // Visit channel - await visit(urls.channelRecording); + await click(commonSelectors.HREF(urls.channelRecording)); // if unauthorized player will not render - assert.dom('.session-recording-player').doesNotExist(); + assert.dom(selectors.SESSION_RECORDING_PLAYER).doesNotExist(); }); test('user cannot view recording without proper authorization: session recording download action', async function (assert) { @@ -117,7 +123,7 @@ module( await visit(urls.channelRecording); // if unauthorized player will not render - assert.dom('.session-recording-player').doesNotExist(); + assert.dom(selectors.SESSION_RECORDING_PLAYER).doesNotExist(); }); test('user cannot view recording if asciicast download errors out', async function (assert) { @@ -134,23 +140,26 @@ module( }, ), ); + await visit(urls.sessionRecording); // Visit channel - await visit(urls.channelRecording); + await click(commonSelectors.HREF(urls.channelRecording)); // if there was an error player will not render - assert.dom('.session-recording-player').doesNotExist(); - assert.dom('.hds-application-state__title').hasText('Playback error'); + assert.dom(selectors.SESSION_RECORDING_PLAYER).doesNotExist(); + assert.dom(commonSelectors.PAGE_MESSAGE_HEADER).hasText('Playback error'); assert .dom(commonSelectors.ALERT_TOAST_BODY) .includesText('rpc error: code = Unknown'); }); test('user can navigate back to session recording screen', async function (assert) { + await visit(urls.sessionRecording); + // Visit channel - await visit(urls.channelRecording); + await click(commonSelectors.HREF(urls.channelRecording)); // click "Back to channels" link in player header - await click('.session-recording-player-header > a'); + await click(selectors.SESSION_RECORDING_PLAYER_LINK); assert.strictEqual(currentURL(), urls.sessionRecording); }); @@ -198,16 +207,17 @@ module( ); await visit(urls.sessionRecording); - assert.dom(DROPDOWN_SELECTOR).doesNotExist(); + assert.dom(selectors.MANAGE_DROPDOWN).doesNotExist(); }); test('user can view manage dropdown with proper authorization', async function (assert) { // Visit channel featuresService.enable('ssh-session-recording'); + await visit(urls.sessionRecordings); - await visit(urls.sessionRecording); + await click(commonSelectors.HREF(urls.sessionRecording)); - assert.dom(DROPDOWN_SELECTOR).isVisible(); + assert.dom(selectors.MANAGE_DROPDOWN).isVisible(); }); test('user can delete a recording with proper authorization', async function (assert) { @@ -217,10 +227,12 @@ module( assert.true( instances.sessionRecording.authorized_actions.includes('delete'), ); - await visit(urls.sessionRecording); - await click(DROPDOWN_SELECTOR); + await visit(urls.sessionRecordings); - await click(DELETE_DROPDOWN_SELECTOR); + await click(commonSelectors.HREF(urls.sessionRecording)); + await click(selectors.MANAGE_DROPDOWN); + + await click(selectors.DELETE_ACTION); assert.strictEqual(currentURL(), urls.sessionRecordings); assert.strictEqual(getRecordingCount(), count - 1); @@ -233,20 +245,23 @@ module( instances.sessionRecording.authorized_actions.filter( (item) => item !== 'delete', ); - await visit(urls.sessionRecording); - assert.dom(DELETE_DROPDOWN_SELECTOR).doesNotExist(); + await visit(urls.sessionRecordings); + + await click(commonSelectors.HREF(urls.sessionRecording)); + + assert.dom(selectors.DELETE_ACTION).doesNotExist(); }); test('both retain until and delete after can be seen with proper authorization', async function (assert) { // Visit channel featuresService.enable('ssh-session-recording'); + await visit(urls.sessionRecordings); - await visit(urls.sessionRecording); + await click(commonSelectors.HREF(urls.sessionRecording)); - assert.dom('.hds-dropdown-toggle-button__text').isVisible(); - - assert.dom('[data-test-retain-until]').isVisible(); - assert.dom('[data-test-delete-after]').isVisible(); + assert.dom(selectors.MANAGE_DROPDOWN).isVisible(); + assert.dom(selectors.RETAIN_UNTIL_OPTION).isVisible(); + assert.dom(selectors.DELETE_AFTER_OPTION).isVisible(); }); }, );