From 539b43325af79b9114faefa8fd0a21c48de32a97 Mon Sep 17 00:00:00 2001 From: Jesse Mazzella Date: Tue, 6 Feb 2024 13:44:01 -0800 Subject: [PATCH] test(e2e): add e2e and visual tests for Mission Status (plus a11y) (#7462) * feat: enable mission status in example user * test: add initial missionStatus suite * test(WIP): mission status e2e suite * test(e2e): add e2e and visual tests for mission status + a11y * test(a11y): scan for a11y violations * a11y: remove labels for non-interactive elements --- .../functional/missionStatus.e2e.spec.js | 127 ++++++++++++++++++ .../visual-a11y/missionStatus.visual.spec.js | 57 ++++++++ example/exampleUser/ExampleUserProvider.js | 41 +++++- .../folderView/components/GridView.vue | 4 +- .../components/MissionStatusPopup.vue | 4 +- .../components/UserIndicator.vue | 17 ++- 6 files changed, 243 insertions(+), 7 deletions(-) create mode 100644 e2e/tests/functional/missionStatus.e2e.spec.js create mode 100644 e2e/tests/visual-a11y/missionStatus.visual.spec.js diff --git a/e2e/tests/functional/missionStatus.e2e.spec.js b/e2e/tests/functional/missionStatus.e2e.spec.js new file mode 100644 index 0000000000..7ae1f61cc8 --- /dev/null +++ b/e2e/tests/functional/missionStatus.e2e.spec.js @@ -0,0 +1,127 @@ +/***************************************************************************** + * Open MCT, Copyright (c) 2014-2024, United States Government + * as represented by the Administrator of the National Aeronautics and Space + * Administration. All rights reserved. + * + * Open MCT is licensed under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * http://www.apache.org/licenses/LICENSE-2.0. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + * Open MCT includes source code licensed under additional open source + * licenses. See the Open Source Licenses file (LICENSES.md) included with + * this source code distribution or the Licensing information page available + * at runtime from the About dialog for additional information. + *****************************************************************************/ + +/* +This test suite is dedicated to tests which verify persistability checks +*/ + +import { fileURLToPath } from 'url'; + +import { expect, test } from '../../baseFixtures.js'; + +test.describe('Mission Status @addInit', () => { + const NO_GO = '0'; + const GO = '1'; + test.beforeEach(async ({ page }) => { + // FIXME: determine if plugins will be added to index.html or need to be injected + await page.addInitScript({ + path: fileURLToPath(new URL('../../helper/addInitExampleUser.js', import.meta.url)) + }); + await page.goto('./', { waitUntil: 'domcontentloaded' }); + await expect(page.getByText('Select Role')).toBeVisible(); + // Description should be empty https://github.com/nasa/openmct/issues/6978 + await expect(page.getByLabel('Dialog message')).toBeHidden(); + // set role + await page.getByRole('button', { name: 'Select', exact: true }).click(); + // dismiss role confirmation popup + await page.getByRole('button', { name: 'Dismiss' }).click(); + }); + + test('Basic functionality', async ({ page }) => { + const imageryStatusSelect = page.getByRole('combobox', { name: 'Imagery' }); + const commandingStatusSelect = page.getByRole('combobox', { name: 'Commanding' }); + const drivingStatusSelect = page.getByRole('combobox', { name: 'Driving' }); + const missionStatusPanel = page.getByRole('dialog', { name: 'User Control Panel' }); + + await test.step('Mission status panel shows/hides when toggled', async () => { + // Ensure that clicking the button toggles the dialog + await page.getByLabel('Toggle Mission Status Panel').click(); + await expect(missionStatusPanel).toBeVisible(); + await page.getByLabel('Toggle Mission Status Panel').click(); + await expect(missionStatusPanel).toBeHidden(); + await page.getByLabel('Toggle Mission Status Panel').click(); + await expect(missionStatusPanel).toBeVisible(); + + // Ensure that clicking the close button closes the dialog + await page.getByLabel('Close Mission Status Panel').click(); + await expect(missionStatusPanel).toBeHidden(); + await page.getByLabel('Toggle Mission Status Panel').click(); + await expect(missionStatusPanel).toBeVisible(); + + // Ensure clicking off the dialog also closes it + await page.getByLabel('My Items Grid View').click(); + await expect(missionStatusPanel).toBeHidden(); + await page.getByLabel('Toggle Mission Status Panel').click(); + await expect(missionStatusPanel).toBeVisible(); + }); + + await test.step('Mission action statuses have correct defaults and can be set', async () => { + await expect(imageryStatusSelect).toHaveValue(NO_GO); + await expect(commandingStatusSelect).toHaveValue(NO_GO); + await expect(drivingStatusSelect).toHaveValue(NO_GO); + + await setMissionStatus(page, 'Imagery', GO); + await expect(imageryStatusSelect).toHaveValue(GO); + await expect(commandingStatusSelect).toHaveValue(NO_GO); + await expect(drivingStatusSelect).toHaveValue(NO_GO); + + await setMissionStatus(page, 'Commanding', GO); + await expect(imageryStatusSelect).toHaveValue(GO); + await expect(commandingStatusSelect).toHaveValue(GO); + await expect(drivingStatusSelect).toHaveValue(NO_GO); + + await setMissionStatus(page, 'Driving', GO); + await expect(imageryStatusSelect).toHaveValue(GO); + await expect(commandingStatusSelect).toHaveValue(GO); + await expect(drivingStatusSelect).toHaveValue(GO); + + await setMissionStatus(page, 'Imagery', NO_GO); + await expect(imageryStatusSelect).toHaveValue(NO_GO); + await expect(commandingStatusSelect).toHaveValue(GO); + await expect(drivingStatusSelect).toHaveValue(GO); + + await setMissionStatus(page, 'Commanding', NO_GO); + await expect(imageryStatusSelect).toHaveValue(NO_GO); + await expect(commandingStatusSelect).toHaveValue(NO_GO); + await expect(drivingStatusSelect).toHaveValue(GO); + + await setMissionStatus(page, 'Driving', NO_GO); + await expect(imageryStatusSelect).toHaveValue(NO_GO); + await expect(commandingStatusSelect).toHaveValue(NO_GO); + await expect(drivingStatusSelect).toHaveValue(NO_GO); + }); + }); +}); + +/** + * + * @param {import('@playwright/test').Page} page + * @param {'Commanding'|'Imagery'|'Driving'} action + * @param {'0'|'1'} status + */ +async function setMissionStatus(page, action, status) { + await page.getByRole('combobox', { name: action }).selectOption(status); + await expect( + page.getByRole('alert').filter({ hasText: 'Successfully set mission status' }) + ).toBeVisible(); + await page.getByLabel('Dismiss').click(); +} diff --git a/e2e/tests/visual-a11y/missionStatus.visual.spec.js b/e2e/tests/visual-a11y/missionStatus.visual.spec.js new file mode 100644 index 0000000000..774b8fdef9 --- /dev/null +++ b/e2e/tests/visual-a11y/missionStatus.visual.spec.js @@ -0,0 +1,57 @@ +/***************************************************************************** + * Open MCT, Copyright (c) 2014-2024, United States Government + * as represented by the Administrator of the National Aeronautics and Space + * Administration. All rights reserved. + * + * Open MCT is licensed under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * http://www.apache.org/licenses/LICENSE-2.0. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + * Open MCT includes source code licensed under additional open source + * licenses. See the Open Source Licenses file (LICENSES.md) included with + * this source code distribution or the Licensing information page available + * at runtime from the About dialog for additional information. + *****************************************************************************/ +import percySnapshot from '@percy/playwright'; +import { fileURLToPath } from 'url'; + +import { expect, scanForA11yViolations, test } from '../../avpFixtures.js'; + +test.describe('Mission Status Visual Tests @a11y', () => { + const GO = '1'; + test.beforeEach(async ({ page }) => { + await page.addInitScript({ + path: fileURLToPath(new URL('../../helper/addInitExampleUser.js', import.meta.url)) + }); + await page.goto('./', { waitUntil: 'domcontentloaded' }); + await expect(page.getByText('Select Role')).toBeVisible(); + // Description should be empty https://github.com/nasa/openmct/issues/6978 + await expect(page.locator('c-message__action-text')).toBeHidden(); + // set role + await page.getByRole('button', { name: 'Select', exact: true }).click(); + // dismiss role confirmation popup + await page.getByRole('button', { name: 'Dismiss' }).click(); + }); + test('Mission status panel', async ({ page, theme }) => { + await page.getByLabel('Toggle Mission Status Panel').click(); + await expect(page.getByRole('dialog', { name: 'User Control Panel' })).toBeVisible(); + await percySnapshot(page, `Mission status panel w/ default statuses (theme: '${theme}')`); + await page.getByRole('combobox', { name: 'Commanding' }).selectOption(GO); + await expect( + page.getByRole('alert').filter({ hasText: 'Successfully set mission status' }) + ).toBeVisible(); + await page.getByLabel('Dismiss').click(); + await percySnapshot(page, `Mission status panel w/ non-default status (theme: '${theme}')`); + }); + + test.afterEach(async ({ page }, testInfo) => { + await scanForA11yViolations(page, testInfo.title); + }); +}); diff --git a/example/exampleUser/ExampleUserProvider.js b/example/exampleUser/ExampleUserProvider.js index d018567e82..d80735abd0 100644 --- a/example/exampleUser/ExampleUserProvider.js +++ b/example/exampleUser/ExampleUserProvider.js @@ -60,10 +60,22 @@ const STATUSES = [ statusFgColor: '#fff' } ]; + +const MISSION_STATUSES = [ + { + key: 0, + label: 'NO GO' + }, + { + key: 1, + label: 'GO' + } +]; /** * @implements {StatusUserProvider} */ export default class ExampleUserProvider extends EventEmitter { + #actionToStatusMap; constructor( openmct, { statusRoles } = { @@ -73,6 +85,11 @@ export default class ExampleUserProvider extends EventEmitter { super(); this.openmct = openmct; + this.#actionToStatusMap = { + Imagery: MISSION_STATUSES[0], + Commanding: MISSION_STATUSES[0], + Driving: MISSION_STATUSES[0] + }; this.user = undefined; this.loggedIn = false; this.autoLoginUser = undefined; @@ -112,7 +129,7 @@ export default class ExampleUserProvider extends EventEmitter { } canSetMissionStatus() { - return Promise.resolve(false); + return Promise.resolve(true); } hasRole(roleId) { @@ -127,6 +144,28 @@ export default class ExampleUserProvider extends EventEmitter { return this.user.getRoles(); } + getPossibleMissionActions() { + return Promise.resolve(Object.keys(this.#actionToStatusMap)); + } + + getPossibleMissionActionStatuses() { + return Promise.resolve(MISSION_STATUSES); + } + + getStatusForMissionAction(action) { + return Promise.resolve(this.#actionToStatusMap[action]); + } + + setStatusForMissionAction(action, status) { + this.#actionToStatusMap[action] = status; + this.emit('missionStatusChange', { + action, + status + }); + + return true; + } + getAllStatusRoles() { return Promise.resolve(this.statusRoles); } diff --git a/src/plugins/folderView/components/GridView.vue b/src/plugins/folderView/components/GridView.vue index fad3d274e8..96804590d7 100644 --- a/src/plugins/folderView/components/GridView.vue +++ b/src/plugins/folderView/components/GridView.vue @@ -20,7 +20,7 @@ at runtime from the About dialog for additional information. -->