From 0340fe18fa576578ac9b1f7fa7faf72a5b9996d7 Mon Sep 17 00:00:00 2001 From: Charles Hacskaylo Date: Wed, 24 Jan 2024 21:18:29 -0800 Subject: [PATCH 1/4] Change Imagery positional freshness label from 'POS' to 'ROV' (#7409) Closes #7404 - Change 'POS' to 'ROV'. --- src/plugins/imagery/components/ImageryView.vue | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/plugins/imagery/components/ImageryView.vue b/src/plugins/imagery/components/ImageryView.vue index 4615b95ad3..543ef0875e 100644 --- a/src/plugins/imagery/components/ImageryView.vue +++ b/src/plugins/imagery/components/ImageryView.vue @@ -145,7 +145,7 @@ v-if="relatedTelemetry.hasRelatedTelemetry && isSpacecraftPositionFresh" class="c-imagery__age icon-check c-imagery--new no-animation" > - POS + ROV From 450cab428fc9c30184fc49da7dba6f601b276851 Mon Sep 17 00:00:00 2001 From: John Hill Date: Wed, 24 Jan 2024 21:26:48 -0800 Subject: [PATCH 2/4] move performance tests to GHA (#7412) * move performance tests to GHA * no need for chrome beta * Add baseline imagery test * skip flaky app * lint --- .circleci/config.yml | 22 ++-- .github/workflows/e2e-perf.yml | 58 +++++++++ .../plugins/plot/overlayPlot.e2e.spec.js | 110 +++++++++--------- e2e/tests/visual-a11y/imagery.visual.spec.js | 93 +++++++++++++++ 4 files changed, 217 insertions(+), 66 deletions(-) create mode 100644 .github/workflows/e2e-perf.yml create mode 100644 e2e/tests/visual-a11y/imagery.visual.spec.js diff --git a/.circleci/config.yml b/.circleci/config.yml index 1388bf5352..c0d6981dbb 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -5,20 +5,20 @@ executors: - image: mcr.microsoft.com/playwright:v1.39.0-focal environment: NODE_ENV: development # Needed to ensure 'dist' folder created and devDependencies installed - PERCY_POSTINSTALL_BROWSER: 'true' # Needed to store the percy browser in cache deps - PERCY_LOGLEVEL: 'debug' # Enable DEBUG level logging for Percy (Issue: https://github.com/nasa/openmct/issues/5742) + PERCY_POSTINSTALL_BROWSER: "true" # Needed to store the percy browser in cache deps + PERCY_LOGLEVEL: "debug" # Enable DEBUG level logging for Percy (Issue: https://github.com/nasa/openmct/issues/5742) ubuntu: machine: image: ubuntu-2204:current docker_layer_caching: true parameters: BUST_CACHE: - description: 'Set this with the CircleCI UI Trigger Workflow button (boolean = true) to bust the cache!' + description: "Set this with the CircleCI UI Trigger Workflow button (boolean = true) to bust the cache!" default: false type: boolean commands: build_and_install: - description: 'All steps used to build and install. Will use cache if found' + description: "All steps used to build and install. Will use cache if found" parameters: node-version: type: string @@ -30,7 +30,7 @@ commands: node-version: << parameters.node-version >> - run: npm install --no-audit --progress=false restore_cache_cmd: - description: 'Custom command for restoring cache with the ability to bust cache. When BUST_CACHE is set to true, jobs will not restore cache' + description: "Custom command for restoring cache with the ability to bust cache. When BUST_CACHE is set to true, jobs will not restore cache" parameters: node-version: type: string @@ -42,7 +42,7 @@ commands: - restore_cache: key: deps--{{ arch }}--{{ .Branch }}--<< parameters.node-version >>--{{ checksum "package.json" }}-{{ checksum ".circleci/config.yml" }} save_cache_cmd: - description: 'Custom command for saving cache.' + description: "Custom command for saving cache." parameters: node-version: type: string @@ -53,7 +53,7 @@ commands: - ~/.npm - node_modules generate_and_store_version_and_filesystem_artifacts: - description: 'Track important packages and files' + description: "Track important packages and files" steps: - run: | [[ $EUID -ne 0 ]] && (sudo mkdir -p /tmp/artifacts && sudo chmod 777 /tmp/artifacts) || (mkdir -p /tmp/artifacts && chmod 777 /tmp/artifacts) @@ -64,7 +64,7 @@ commands: - store_artifacts: path: /tmp/artifacts/ generate_e2e_code_cov_report: - description: 'Generate e2e code coverage artifacts and publish to codecov.io. Needed to that we can ignore the exit code status of the npm run test' + description: "Generate e2e code coverage artifacts and publish to codecov.io. Needed to that we can ignore the exit code status of the npm run test" parameters: suite: type: string @@ -129,7 +129,7 @@ jobs: node-version: lts/hydrogen - when: #Only install chrome-beta when running the 'full' suite to save $$$ condition: - equal: ['full', <>] + equal: ["full", <>] steps: - run: npx playwright install chrome-beta - run: SHARD="$((${CIRCLE_NODE_INDEX}+1))"; npm run test:e2e:<> -- --shard=${SHARD}/${CIRCLE_NODE_TOTAL} @@ -251,8 +251,6 @@ workflows: - e2e-test: name: e2e-stable suite: stable - - mem-test - - perf-test - visual-a11y-tests: name: visual-test-ci suite: ci @@ -278,7 +276,7 @@ workflows: - e2e-couchdb triggers: - schedule: - cron: '0 0 * * *' + cron: "0 0 * * *" filters: branches: only: diff --git a/.github/workflows/e2e-perf.yml b/.github/workflows/e2e-perf.yml new file mode 100644 index 0000000000..2255bde61b --- /dev/null +++ b/.github/workflows/e2e-perf.yml @@ -0,0 +1,58 @@ +name: 'e2e-perf' +on: + push: + branches: master + workflow_dispatch: + pull_request: + types: + - labeled + - opened + schedule: + - cron: '0 0 * * *' +jobs: + e2e-full: + if: contains(github.event.pull_request.labels.*.name, 'pr:e2e:perf') || github.event_name == 'schedule' || github.event_name == 'workflow_dispatch' + runs-on: ubuntu-latest + timeout-minutes: 60 + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-node@v4 + with: + node-version: 'lts/hydrogen' + + - name: Cache NPM dependencies + uses: actions/cache@v3 + with: + path: ~/.npm + key: ${{ runner.os }}-node-${{ hashFiles('**/package.json') }} + restore-keys: | + ${{ runner.os }}-node- + + - run: npx playwright@1.39.0 install + - run: npm install --cache ~/.npm --no-audit --progress=false + - run: npm run test:perf:localhost + - run: npm run test:perf:contract + - run: npm run test:perf:memory + - name: Archive test results + if: success() || failure() + uses: actions/upload-artifact@v3 + with: + path: test-results + + - name: Remove pr:e2e:perf label (if present) + if: always() + uses: actions/github-script@v6 + with: + script: | + const { owner, repo, number } = context.issue; + const labelToRemove = 'pr:e2e:perf'; + try { + await github.rest.issues.removeLabel({ + owner, + repo, + issue_number: number, + name: labelToRemove + }); + } catch (error) { + core.warning(`Failed to remove ' + labelToRemove + ' label: ${error.message}`); + } diff --git a/e2e/tests/functional/plugins/plot/overlayPlot.e2e.spec.js b/e2e/tests/functional/plugins/plot/overlayPlot.e2e.spec.js index a80f15be0a..52c6f0fb3f 100644 --- a/e2e/tests/functional/plugins/plot/overlayPlot.e2e.spec.js +++ b/e2e/tests/functional/plugins/plot/overlayPlot.e2e.spec.js @@ -63,78 +63,80 @@ test.describe('Overlay Plot', () => { await expect(seriesColorSwatch).toHaveCSS('background-color', 'rgb(255, 166, 61)'); }); - test('Limit lines persist when series is moved to another Y Axis and on refresh', async ({ - page - }) => { - test.info().annotations.push({ - type: 'issue', - description: 'https://github.com/nasa/openmct/issues/6338' - }); - // Create an Overlay Plot with a default SWG - const overlayPlot = await createDomainObjectWithDefaults(page, { - type: 'Overlay Plot' - }); + //skipping due to https://github.com/nasa/openmct/issues/7405 + test.fixme( + 'Limit lines persist when series is moved to another Y Axis and on refresh', + async ({ page }) => { + test.info().annotations.push({ + type: 'issue', + description: 'https://github.com/nasa/openmct/issues/6338' + }); + // Create an Overlay Plot with a default SWG + const overlayPlot = await createDomainObjectWithDefaults(page, { + type: 'Overlay Plot' + }); - const swgA = await createDomainObjectWithDefaults(page, { - type: 'Sine Wave Generator', - parent: overlayPlot.uuid - }); + const swgA = await createDomainObjectWithDefaults(page, { + type: 'Sine Wave Generator', + parent: overlayPlot.uuid + }); - await page.goto(overlayPlot.url); + await page.goto(overlayPlot.url); - // Assert that no limit lines are shown by default - await page.waitForSelector('.js-limit-area', { state: 'attached' }); - expect(await page.locator('.c-plot-limit-line').count()).toBe(0); + // Assert that no limit lines are shown by default + await page.waitForSelector('.js-limit-area', { state: 'attached' }); + expect(await page.locator('.c-plot-limit-line').count()).toBe(0); - // Enter edit mode - await page.getByLabel('Edit Object').click(); + // Enter edit mode + await page.getByLabel('Edit Object').click(); - // Expand the "Sine Wave Generator" plot series options and enable limit lines - await page.getByRole('tab', { name: 'Config' }).click(); - await page - .getByRole('list', { name: 'Plot Series Properties' }) - .locator('span') - .first() - .click(); - await page - .getByRole('list', { name: 'Plot Series Properties' }) - .locator('[title="Display limit lines"]~div input') - .check(); + // Expand the "Sine Wave Generator" plot series options and enable limit lines + await page.getByRole('tab', { name: 'Config' }).click(); + await page + .getByRole('list', { name: 'Plot Series Properties' }) + .locator('span') + .first() + .click(); + await page + .getByRole('list', { name: 'Plot Series Properties' }) + .locator('[title="Display limit lines"]~div input') + .check(); - await assertLimitLinesExistAndAreVisible(page); + await assertLimitLinesExistAndAreVisible(page); - // Save (exit edit mode) - await page.locator('button[title="Save"]').click(); - await page.getByRole('listitem', { name: 'Save and Finish Editing' }).click(); + // Save (exit edit mode) + await page.locator('button[title="Save"]').click(); + await page.getByRole('listitem', { name: 'Save and Finish Editing' }).click(); - await assertLimitLinesExistAndAreVisible(page); + await assertLimitLinesExistAndAreVisible(page); - await page.reload(); + await page.reload(); - await assertLimitLinesExistAndAreVisible(page); + await assertLimitLinesExistAndAreVisible(page); - // Enter edit mode - await page.getByLabel('Edit Object').click(); + // Enter edit mode + await page.getByLabel('Edit Object').click(); - await page.getByRole('tab', { name: 'Elements' }).click(); + await page.getByRole('tab', { name: 'Elements' }).click(); - // Drag Sine Wave Generator series from Y Axis 1 into Y Axis 2 - await page - .locator(`#inspector-elements-tree >> text=${swgA.name}`) - .dragTo(page.locator('[aria-label="Element Item Group Y Axis 2"]')); + // Drag Sine Wave Generator series from Y Axis 1 into Y Axis 2 + await page + .locator(`#inspector-elements-tree >> text=${swgA.name}`) + .dragTo(page.locator('[aria-label="Element Item Group Y Axis 2"]')); - await assertLimitLinesExistAndAreVisible(page); + await assertLimitLinesExistAndAreVisible(page); - // Save (exit edit mode) - await page.locator('button[title="Save"]').click(); - await page.getByRole('listitem', { name: 'Save and Finish Editing' }).click(); + // Save (exit edit mode) + await page.locator('button[title="Save"]').click(); + await page.getByRole('listitem', { name: 'Save and Finish Editing' }).click(); - await assertLimitLinesExistAndAreVisible(page); + await assertLimitLinesExistAndAreVisible(page); - await page.reload(); + await page.reload(); - await assertLimitLinesExistAndAreVisible(page); - }); + await assertLimitLinesExistAndAreVisible(page); + } + ); test('The elements pool supports dragging series into multiple y-axis buckets', async ({ page diff --git a/e2e/tests/visual-a11y/imagery.visual.spec.js b/e2e/tests/visual-a11y/imagery.visual.spec.js new file mode 100644 index 0000000000..78de781dd5 --- /dev/null +++ b/e2e/tests/visual-a11y/imagery.visual.spec.js @@ -0,0 +1,93 @@ +/***************************************************************************** + * 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 { createDomainObjectWithDefaults, setRealTimeMode } from '../../appActions.js'; +import { VISUAL_URL } from '../../constants.js'; +import { expect, test } from '../../pluginFixtures.js'; + +test.describe('Visual - Example Imagery', () => { + let exampleImagery; + let parentLayout; + + test.beforeEach(async ({ page }) => { + await page.goto(VISUAL_URL, { waitUntil: 'domcontentloaded' }); + + parentLayout = await createDomainObjectWithDefaults(page, { + type: 'Display Layout', + name: 'Parent Layout' + }); + + exampleImagery = await createDomainObjectWithDefaults(page, { + type: 'Example Imagery', + name: 'Example Imagery Test', + parent: parentLayout.uuid + }); + + // Modify Example Imagery to create a really stable Example Imagery + await page.goto(exampleImagery.url, { waitUntil: 'domcontentloaded' }); + await page.getByRole('button', { name: 'More actions' }).click(); + await page.getByRole('menuitem', { name: 'Edit Properties...' }).click(); + await page + .locator('#imageLocation-textarea') + .fill( + 'https://www.nasa.gov/wp-content/uploads/static/history/alsj/a16/AS16-117-18731.jpg,https://www.nasa.gov/wp-content/uploads/static/history/alsj/a16/AS16-117-18731.jpg,https://www.nasa.gov/wp-content/uploads/static/history/alsj/a16/AS16-117-18731.jpg,https://www.nasa.gov/wp-content/uploads/static/history/alsj/a16/AS16-117-18731.jpg,https://www.nasa.gov/wp-content/uploads/static/history/alsj/a16/AS16-117-18731.jpg,https://www.nasa.gov/wp-content/uploads/static/history/alsj/a16/AS16-117-18731.jpg,https://www.nasa.gov/wp-content/uploads/static/history/alsj/a16/AS16-117-18731.jpg,https://www.nasa.gov/wp-content/uploads/static/history/alsj/a16/AS16-117-18731.jpg,https://www.nasa.gov/wp-content/uploads/static/history/alsj/a16/AS16-117-18731.jpg,https://www.nasa.gov/wp-content/uploads/static/history/alsj/a16/AS16-117-18731.jpg,https://www.nasa.gov/wp-content/uploads/static/history/alsj/a16/AS16-117-18731.jpg,https://www.nasa.gov/wp-content/uploads/static/history/alsj/a16/AS16-117-18731.jpg,https://www.nasa.gov/wp-content/uploads/static/history/alsj/a16/AS16-117-18731.jpg,https://www.nasa.gov/wp-content/uploads/static/history/alsj/a16/AS16-117-18731.jpg,https://www.nasa.gov/wp-content/uploads/static/history/alsj/a16/AS16-117-18731.jpg,https://www.nasa.gov/wp-content/uploads/static/history/alsj/a16/AS16-117-18731.jpg,https://www.nasa.gov/wp-content/uploads/static/history/alsj/a16/AS16-117-18731.jpg' + ); + await page.getByRole('button', { name: 'Save' }).click(); + await page.reload({ waitUntil: 'domcontentloaded' }); + await page.getByTitle('Collapse Browse Pane').click(); + await page.getByTitle('Collapse Inspect Pane').click(); + }); + + test('Example Imagery in Fixed Time', async ({ page, theme }) => { + await page.goto(exampleImagery.url, { waitUntil: 'domcontentloaded' }); + + await expect(page.getByLabel('Image Wrapper')).toBeVisible(); + + await percySnapshot(page, `Example Imagery in Fixed Time (theme: ${theme})`); + + await page.getByLabel('Image Wrapper').hover(); + + await percySnapshot(page, `Example Imagery Hover in Fixed Time (theme: ${theme})`); + }); + + test('Example Imagery in Real Time', async ({ page, theme }) => { + await page.goto(exampleImagery.url, { waitUntil: 'domcontentloaded' }); + + await setRealTimeMode(page, true); + //Temporary to close the dialog + await page.getByLabel('Submit time offsets').click(); + + await expect(page.getByLabel('Image Wrapper')).toBeVisible(); + + await percySnapshot(page, `Example Imagery in Real Time (theme: ${theme})`); + }); + + test('Example Imagery in Display Layout', async ({ page, theme }) => { + await page.goto(parentLayout.url, { waitUntil: 'domcontentloaded' }); + + await expect(page.getByLabel('Image Wrapper')).toBeVisible(); + + await percySnapshot(page, `Example Imagery in Display Layout (theme: ${theme})`); + }); +}); From 2b2c74da9cdda4a1caebd7d7b7cefe8bc45c813c Mon Sep 17 00:00:00 2001 From: Scott Bell Date: Thu, 25 Jan 2024 06:36:44 +0100 Subject: [PATCH 3/4] New action to reload an individual view and all of its children (#7362) * add reload action plugin * checking for domain object before reloading * check if objects are equal before refreshing * add test * lint * change to label * ensure object styles are initialized * resubscribe to staleness too * add better labels for tabels * ensure tab uses exact for label now due to table aria changes * fix table tests * make tabs exact * update conflicts --------- Co-authored-by: Jesse Mazzella --- .../reloadAction/reloadAction.e2e.spec.js | 125 ++++++++++++++++++ .../functional/plugins/tabs/tabs.e2e.spec.js | 8 +- .../telemetryTable/telemetryTable.e2e.spec.js | 19 ++- e2e/tests/performance/tabs.e2e.spec.js | 12 +- src/MCT.js | 1 + src/plugins/plugins.js | 2 + src/plugins/reloadAction/ReloadAction.js | 37 ++++++ src/plugins/reloadAction/plugin.js | 28 ++++ .../telemetryTable/components/TableCell.vue | 2 +- .../components/TableComponent.vue | 1 + src/ui/components/ObjectView.vue | 10 ++ 11 files changed, 227 insertions(+), 18 deletions(-) create mode 100644 e2e/tests/functional/plugins/reloadAction/reloadAction.e2e.spec.js create mode 100644 src/plugins/reloadAction/ReloadAction.js create mode 100644 src/plugins/reloadAction/plugin.js diff --git a/e2e/tests/functional/plugins/reloadAction/reloadAction.e2e.spec.js b/e2e/tests/functional/plugins/reloadAction/reloadAction.e2e.spec.js new file mode 100644 index 0000000000..c64e19a007 --- /dev/null +++ b/e2e/tests/functional/plugins/reloadAction/reloadAction.e2e.spec.js @@ -0,0 +1,125 @@ +/***************************************************************************** + * Open MCT, Copyright (c) 2014-2023, 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 { createDomainObjectWithDefaults, expandEntireTree } from '../../../../appActions.js'; +import { expect, test } from '../../../../pluginFixtures.js'; + +test.describe('Reload action', () => { + test.beforeEach(async ({ page }) => { + await page.goto('./', { waitUntil: 'domcontentloaded' }); + + const displayLayout = await createDomainObjectWithDefaults(page, { + type: 'Display Layout' + }); + + const alphaTable = await createDomainObjectWithDefaults(page, { + type: 'Telemetry Table', + name: 'Alpha Table' + }); + + const betaTable = await createDomainObjectWithDefaults(page, { + type: 'Telemetry Table', + name: 'Beta Table' + }); + + await createDomainObjectWithDefaults(page, { + type: 'Sine Wave Generator', + parent: alphaTable.uuid, + customParameters: { + '[aria-label="Data Rate (hz)"]': '0.001' + } + }); + + await createDomainObjectWithDefaults(page, { + type: 'Sine Wave Generator', + parent: betaTable.uuid, + customParameters: { + '[aria-label="Data Rate (hz)"]': '0.001' + } + }); + + await page.goto(displayLayout.url); + + // Expand all folders + await expandEntireTree(page); + + await page.getByLabel('Edit Object', { exact: true }).click(); + + await page.dragAndDrop(`text='Alpha Table'`, '.l-layout__grid-holder', { + targetPosition: { x: 0, y: 0 } + }); + + await page.dragAndDrop(`text='Beta Table'`, '.l-layout__grid-holder', { + targetPosition: { x: 0, y: 250 } + }); + + await page.locator('button[title="Save"]').click(); + await page.getByRole('listitem', { name: 'Save and Finish Editing' }).click(); + }); + + test('can reload display layout and its children', async ({ page }) => { + const beforeReloadAlphaTelemetryValue = await page + .getByLabel('Alpha Table table content') + .getByLabel('wavelengths table cell') + .first() + .getAttribute('title'); + const beforeReloadBetaTelemetryValue = await page + .getByLabel('Beta Table table content') + .getByLabel('wavelengths table cell') + .first() + .getAttribute('title'); + // reload alpha + await page.getByTitle('View menu items').first().click(); + await page.getByRole('menuitem', { name: /Reload/ }).click(); + + const afterReloadAlphaTelemetryValue = await page + .getByLabel('Alpha Table table content') + .getByLabel('wavelengths table cell') + .first() + .getAttribute('title'); + const afterReloadBetaTelemetryValue = await page + .getByLabel('Beta Table table content') + .getByLabel('wavelengths table cell') + .first() + .getAttribute('title'); + + expect(beforeReloadAlphaTelemetryValue).not.toEqual(afterReloadAlphaTelemetryValue); + expect(beforeReloadBetaTelemetryValue).toEqual(afterReloadBetaTelemetryValue); + + // now reload parent + await page.getByTitle('More actions').click(); + await page.getByRole('menuitem', { name: /Reload/ }).click(); + + const fullReloadAlphaTelemetryValue = await page + .getByLabel('Alpha Table table content') + .getByLabel('wavelengths table cell') + .first() + .getAttribute('title'); + const fullReloadBetaTelemetryValue = await page + .getByLabel('Beta Table table content') + .getByLabel('wavelengths table cell') + .first() + .getAttribute('title'); + + expect(fullReloadAlphaTelemetryValue).not.toEqual(afterReloadAlphaTelemetryValue); + expect(fullReloadBetaTelemetryValue).not.toEqual(afterReloadBetaTelemetryValue); + }); +}); diff --git a/e2e/tests/functional/plugins/tabs/tabs.e2e.spec.js b/e2e/tests/functional/plugins/tabs/tabs.e2e.spec.js index 7d5df80fb8..9f8c9eb8c7 100644 --- a/e2e/tests/functional/plugins/tabs/tabs.e2e.spec.js +++ b/e2e/tests/functional/plugins/tabs/tabs.e2e.spec.js @@ -50,7 +50,7 @@ test.describe('Tabs View', () => { page.goto(tabsView.url); // select first tab - await page.getByLabel(`${table.name} tab`).click(); + await page.getByLabel(`${table.name} tab`, { exact: true }).click(); // ensure table header visible await expect(page.getByRole('searchbox', { name: 'message filter input' })).toBeVisible(); @@ -58,7 +58,7 @@ test.describe('Tabs View', () => { await expect(page.locator('canvas[id=webglContext]')).toBeHidden(); // select second tab - await page.getByLabel(`${notebook.name} tab`).click(); + await page.getByLabel(`${notebook.name} tab`, { exact: true }).click(); // ensure notebook visible await expect(page.locator('.c-notebook__drag-area')).toBeVisible(); @@ -67,7 +67,7 @@ test.describe('Tabs View', () => { await expect(page.locator('canvas[id=webglContext]')).toBeHidden(); // select third tab - await page.getByLabel(`${sineWaveGenerator.name} tab`).click(); + await page.getByLabel(`${sineWaveGenerator.name} tab`, { exact: true }).click(); // expect sine wave generator visible await expect(page.locator('.c-plot')).toBeVisible(); @@ -78,7 +78,7 @@ test.describe('Tabs View', () => { await expect(page.locator('canvas').nth(1)).toBeVisible(); // now try to select the first tab again - await page.getByLabel(`${table.name} tab`).click(); + await page.getByLabel(`${table.name} tab`, { exact: true }).click(); // ensure table header visible await expect(page.getByRole('searchbox', { name: 'message filter input' })).toBeVisible(); diff --git a/e2e/tests/functional/plugins/telemetryTable/telemetryTable.e2e.spec.js b/e2e/tests/functional/plugins/telemetryTable/telemetryTable.e2e.spec.js index 1bc7e062b6..4ce30f034e 100644 --- a/e2e/tests/functional/plugins/telemetryTable/telemetryTable.e2e.spec.js +++ b/e2e/tests/functional/plugins/telemetryTable/telemetryTable.e2e.spec.js @@ -64,10 +64,9 @@ test.describe('Telemetry Table', () => { // Get the most recent telemetry date const latestTelemetryDate = await page - .locator('table.c-telemetry-table__body > tbody > tr') + .getByLabel('table content') + .getByLabel('utc table cell') .last() - .locator('td') - .nth(1) .getAttribute('title'); // Verify that it is <= our new end bound @@ -91,7 +90,7 @@ test.describe('Telemetry Table', () => { await page.getByRole('searchbox', { name: 'message filter input' }).click(); await page.getByRole('searchbox', { name: 'message filter input' }).fill('Roger'); - let cells = await page.getByRole('cell', { name: /Roger/ }).all(); + let cells = await page.getByRole('cell').getByText(/Roger/).all(); // ensure we've got more than one cell expect(cells.length).toBeGreaterThan(1); // ensure the text content of each cell contains the search term @@ -103,7 +102,10 @@ test.describe('Telemetry Table', () => { await page.getByRole('searchbox', { name: 'message filter input' }).click(); await page.getByRole('searchbox', { name: 'message filter input' }).fill('Dodger'); - cells = await page.getByRole('cell', { name: /Dodger/ }).all(); + cells = await page + .getByRole('cell') + .getByText(/Dodger/) + .all(); // ensure we've got more than one cell expect(cells.length).toBe(0); // ensure the text content of each cell contains the search term @@ -135,7 +137,7 @@ test.describe('Telemetry Table', () => { await page.getByRole('searchbox', { name: 'message filter input' }).click(); await page.getByRole('searchbox', { name: 'message filter input' }).fill('/[Rr]oger/'); - let cells = await page.getByRole('cell', { name: /Roger/ }).all(); + let cells = await page.getByRole('cell').getByText(/Roger/).all(); // ensure we've got more than one cell expect(cells.length).toBeGreaterThan(1); // ensure the text content of each cell contains the search term @@ -147,7 +149,10 @@ test.describe('Telemetry Table', () => { await page.getByRole('searchbox', { name: 'message filter input' }).click(); await page.getByRole('searchbox', { name: 'message filter input' }).fill('/[Dd]oger/'); - cells = await page.getByRole('cell', { name: /Dodger/ }).all(); + cells = await page + .getByRole('cell') + .getByText(/Dodger/) + .all(); // ensure we've got more than one cell expect(cells.length).toBe(0); // ensure the text content of each cell contains the search term diff --git a/e2e/tests/performance/tabs.e2e.spec.js b/e2e/tests/performance/tabs.e2e.spec.js index 9689be8a38..3db219f1da 100644 --- a/e2e/tests/performance/tabs.e2e.spec.js +++ b/e2e/tests/performance/tabs.e2e.spec.js @@ -24,7 +24,7 @@ import { createDomainObjectWithDefaults, waitForPlotsToRender } from '../../appA import { expect, test } from '../../pluginFixtures.js'; test.describe('Tabs View', () => { - test('Renders tabbed elements nicely', async ({ page }) => { + test('Renders tabbed elements only when visible', async ({ page }) => { // Code to hook into the requestAnimationFrame function and log each call let animationCalls = []; await page.exposeFunction('logCall', (callCount) => { @@ -64,24 +64,24 @@ test.describe('Tabs View', () => { page.goto(tabsView.url); // select first tab - await page.getByLabel(`${table.name} tab`).click(); + await page.getByLabel(`${table.name} tab`, { exact: true }).click(); // ensure table header visible await expect(page.getByRole('searchbox', { name: 'message filter input' })).toBeVisible(); // select second tab - await page.getByLabel(`${notebook.name} tab`).click(); + await page.getByLabel(`${notebook.name} tab`, { exact: true }).click(); // expect notebook visible await expect(page.locator('.c-notebook__drag-area')).toBeVisible(); // select third tab - await page.getByLabel(`${sineWaveGenerator.name} tab`).click(); + await page.getByLabel(`${sineWaveGenerator.name} tab`, { exact: true }).click(); // ensure sine wave generator visible expect(await page.locator('.c-plot').isVisible()).toBe(true); // now select notebook and clear animation calls - await page.getByLabel(`${notebook.name} tab`).click(); + await page.getByLabel(`${notebook.name} tab`, { exact: true }).click(); animationCalls = []; // expect notebook visible await expect(page.locator('.c-notebook__drag-area')).toBeVisible(); @@ -89,7 +89,7 @@ test.describe('Tabs View', () => { // select sine wave generator and clear animation calls animationCalls = []; - await page.getByLabel(`${sineWaveGenerator.name} tab`).click(); + await page.getByLabel(`${sineWaveGenerator.name} tab`, { exact: true }).click(); // ensure sine wave generator visible await waitForPlotsToRender(page); diff --git a/src/MCT.js b/src/MCT.js index 5640995739..0a011b33ab 100644 --- a/src/MCT.js +++ b/src/MCT.js @@ -251,6 +251,7 @@ export class MCT extends EventEmitter { this.install(this.plugins.FlexibleLayout()); this.install(this.plugins.GoToOriginalAction()); this.install(this.plugins.OpenInNewTabAction()); + this.install(this.plugins.ReloadAction()); this.install(this.plugins.WebPage()); this.install(this.plugins.Condition()); this.install(this.plugins.ConditionWidget()); diff --git a/src/plugins/plugins.js b/src/plugins/plugins.js index c1c2e539ad..fb7887c9f3 100644 --- a/src/plugins/plugins.js +++ b/src/plugins/plugins.js @@ -65,6 +65,7 @@ import PerformanceIndicator from './performanceIndicator/plugin.js'; import CouchDBPlugin from './persistence/couch/plugin.js'; import PlanLayout from './plan/plugin.js'; import PlotPlugin from './plot/plugin.js'; +import ReloadAction from './reloadAction/plugin.js'; import RemoteClock from './remoteClock/plugin.js'; import StaticRootPlugin from './staticRootPlugin/plugin.js'; import SummaryWidget from './summaryWidget/plugin.js'; @@ -141,6 +142,7 @@ plugins.Filters = Filters; plugins.ObjectMigration = ObjectMigration; plugins.GoToOriginalAction = GoToOriginalAction; plugins.OpenInNewTabAction = OpenInNewTabAction; +plugins.ReloadAction = ReloadAction; plugins.ClearData = ClearData; plugins.WebPage = WebPagePlugin; plugins.Espresso = Espresso; diff --git a/src/plugins/reloadAction/ReloadAction.js b/src/plugins/reloadAction/ReloadAction.js new file mode 100644 index 0000000000..ff86bff4f5 --- /dev/null +++ b/src/plugins/reloadAction/ReloadAction.js @@ -0,0 +1,37 @@ +/***************************************************************************** + * Open MCT, Copyright (c) 2014-2023, 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. + *****************************************************************************/ +export default class ReloadAction { + constructor(openmct) { + this.name = 'Reload'; + this.key = 'reload'; + this.description = 'Reload this object and its children'; + this.group = 'action'; + this.priority = 10; + this.cssClass = 'icon-refresh'; + + this.openmct = openmct; + } + invoke(objectPath, view) { + const domainObject = objectPath[0]; + this.openmct.objectViews.emit('reload', domainObject); + } +} diff --git a/src/plugins/reloadAction/plugin.js b/src/plugins/reloadAction/plugin.js new file mode 100644 index 0000000000..c9db76b89a --- /dev/null +++ b/src/plugins/reloadAction/plugin.js @@ -0,0 +1,28 @@ +/***************************************************************************** + * Open MCT, Copyright (c) 2014-2023, 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 ReloadAction from './ReloadAction.js'; + +export default function plugin() { + return function install(openmct) { + openmct.actions.register(new ReloadAction(openmct)); + }; +} diff --git a/src/plugins/telemetryTable/components/TableCell.vue b/src/plugins/telemetryTable/components/TableCell.vue index 4a4fa81ff8..e30b64ce09 100644 --- a/src/plugins/telemetryTable/components/TableCell.vue +++ b/src/plugins/telemetryTable/components/TableCell.vue @@ -22,8 +22,8 @@