diff --git a/e2e/test-data/VisualTestData_storage.json b/e2e/test-data/VisualTestData_storage.json new file mode 100644 index 0000000000..d059303264 --- /dev/null +++ b/e2e/test-data/VisualTestData_storage.json @@ -0,0 +1,22 @@ +{ + "cookies": [], + "origins": [ + { + "origin": "http://localhost:8080", + "localStorage": [ + { + "name": "tcHistory", + "value": "{\"utc\":[{\"start\":1654548551471,\"end\":1654550351471}]}" + }, + { + "name": "mct", + "value": "{\"mine\":{\"identifier\":{\"key\":\"mine\",\"namespace\":\"\"},\"name\":\"My Items\",\"type\":\"folder\",\"composition\":[{\"key\":\"527856c0-cced-4b64-bb19-f943432326d0\",\"namespace\":\"\"}],\"location\":\"ROOT\",\"persisted\":1654550352296,\"modified\":1654550352296},\"527856c0-cced-4b64-bb19-f943432326d0\":{\"identifier\":{\"key\":\"527856c0-cced-4b64-bb19-f943432326d0\",\"namespace\":\"\"},\"name\":\"Unnamed Overlay Plot\",\"type\":\"telemetry.plot.overlay\",\"composition\":[{\"key\":\"ce88ce37-8bb9-45e1-a85b-bb7e3c8453b9\",\"namespace\":\"\"}],\"configuration\":{\"series\":[{\"identifier\":{\"key\":\"ce88ce37-8bb9-45e1-a85b-bb7e3c8453b9\",\"namespace\":\"\"}}],\"yAxis\":{},\"xAxis\":{}},\"modified\":1654550353356,\"location\":\"mine\",\"persisted\":1654550353357},\"ce88ce37-8bb9-45e1-a85b-bb7e3c8453b9\":{\"name\":\"Unnamed Sine Wave Generator\",\"type\":\"generator\",\"identifier\":{\"key\":\"ce88ce37-8bb9-45e1-a85b-bb7e3c8453b9\",\"namespace\":\"\"},\"telemetry\":{\"period\":10,\"amplitude\":1,\"offset\":0,\"dataRateInHz\":1,\"phase\":0,\"randomness\":0,\"loadDelay\":\"5000\"},\"modified\":1654550353350,\"location\":\"527856c0-cced-4b64-bb19-f943432326d0\",\"persisted\":1654550353350}}" + }, + { + "name": "mct-tree-expanded", + "value": "[\"/browse/mine\"]" + } + ] + } + ] +} \ No newline at end of file diff --git a/e2e/test-data/recycled_local_storage.json b/e2e/test-data/recycled_local_storage.json new file mode 100644 index 0000000000..5026bca3bb --- /dev/null +++ b/e2e/test-data/recycled_local_storage.json @@ -0,0 +1,22 @@ +{ + "cookies": [], + "origins": [ + { + "origin": "http://localhost:8080", + "localStorage": [ + { + "name": "tcHistory", + "value": "{\"utc\":[{\"start\":1654537164464,\"end\":1654538964464},{\"start\":1652301954635,\"end\":1652303754635}]}" + }, + { + "name": "mct", + "value": "{\"mine\":{\"identifier\":{\"key\":\"mine\",\"namespace\":\"\"},\"name\":\"My Items\",\"type\":\"folder\",\"composition\":[{\"key\":\"f64bea3b-58a7-4586-8c05-8b651e5f0bfd\",\"namespace\":\"\"},{\"key\":\"2d02a680-eb7e-4645-bba2-dd298f76efb8\",\"namespace\":\"\"}],\"location\":\"ROOT\",\"persisted\":1654538965703,\"modified\":1654538965703},\"f64bea3b-58a7-4586-8c05-8b651e5f0bfd\":{\"name\":\"Unnamed Condition Set\",\"type\":\"conditionSet\",\"identifier\":{\"key\":\"f64bea3b-58a7-4586-8c05-8b651e5f0bfd\",\"namespace\":\"\"},\"configuration\":{\"conditionTestData\":[],\"conditionCollection\":[{\"isDefault\":true,\"id\":\"73f2d9ae-d1f3-4561-b7fc-ecd5df557249\",\"configuration\":{\"name\":\"Default\",\"output\":\"Default\",\"trigger\":\"all\",\"criteria\":[]},\"summary\":\"Default condition\"}]},\"composition\":[],\"telemetry\":{},\"modified\":1652303755999,\"location\":\"mine\",\"persisted\":1652303756002},\"2d02a680-eb7e-4645-bba2-dd298f76efb8\":{\"name\":\"Unnamed Condition Set\",\"type\":\"conditionSet\",\"identifier\":{\"key\":\"2d02a680-eb7e-4645-bba2-dd298f76efb8\",\"namespace\":\"\"},\"configuration\":{\"conditionTestData\":[],\"conditionCollection\":[{\"isDefault\":true,\"id\":\"4291d80c-303c-4d8d-85e1-10f012b864fb\",\"configuration\":{\"name\":\"Default\",\"output\":\"Default\",\"trigger\":\"all\",\"criteria\":[]},\"summary\":\"Default condition\"}]},\"composition\":[],\"telemetry\":{},\"modified\":1654538965702,\"location\":\"mine\",\"persisted\":1654538965702}}" + }, + { + "name": "mct-tree-expanded", + "value": "[]" + } + ] + } + ] +} \ No newline at end of file diff --git a/e2e/tests/plugins/condition/condition.e2e.spec.js b/e2e/tests/plugins/condition/condition.e2e.spec.js index a2ace18f75..c7c2749068 100644 --- a/e2e/tests/plugins/condition/condition.e2e.spec.js +++ b/e2e/tests/plugins/condition/condition.e2e.spec.js @@ -52,7 +52,7 @@ test.describe.serial('Condition Set CRUD Operations on @localStorage', () => { ]); //Save localStorage for future test execution - await context.storageState({ path: './e2e/tests/recycled_storage.json' }); + await context.storageState({ path: './e2e/test-data/recycled_local_storage.json' }); //Set object identifier from url conditionSetUrl = await page.url(); @@ -65,7 +65,7 @@ test.describe.serial('Condition Set CRUD Operations on @localStorage', () => { await browser.close(); }); //Load localStorage for subsequent tests - test.use({ storageState: './e2e/tests/recycled_storage.json' }); + test.use({ storageState: './e2e/test-data/recycled_local_storage.json' }); //Begin suite of tests again localStorage test('Condition set object properties persist in main view and inspector', async ({ page }) => { //Navigate to baseURL with injected localStorage diff --git a/e2e/tests/visual/controlledClock.visual.spec.js b/e2e/tests/visual/controlledClock.visual.spec.js new file mode 100644 index 0000000000..7eb7f64c84 --- /dev/null +++ b/e2e/tests/visual/controlledClock.visual.spec.js @@ -0,0 +1,70 @@ +/***************************************************************************** + * Open MCT, Copyright (c) 2014-2022, 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. + *****************************************************************************/ + +/* +Collection of Visual Tests set to run in a default context. The tests within this suite +are only meant to run against openmct's app.js started by `npm run start` within the +`./e2e/playwright-visual.config.js` file. + +These should only use functional expect statements to verify assumptions about the state +in a test and not for functional verification of correctness. Visual tests are not supposed +to "fail" on assertions. Instead, they should be used to detect changes between builds or branches. + +Note: Larger testsuite sizes are OK due to the setup time associated with these tests. +*/ + +const { test, expect } = require('@playwright/test'); +const percySnapshot = require('@percy/playwright'); +const path = require('path'); +const sinon = require('sinon'); + +// Snippet from https://github.com/microsoft/playwright/issues/6347#issuecomment-965887758 +// Will replace with cy.clock() equivalent +test.beforeEach(async ({ context }) => { + await context.addInitScript({ + // eslint-disable-next-line no-undef + path: path.join(__dirname, '../../..', './node_modules/sinon/pkg/sinon.js') + }); + await context.addInitScript(() => { + window.__clock = sinon.useFakeTimers({ + now: 0, //Set browser clock to UNIX Epoch + shouldAdvanceTime: false, //Don't advance the clock + toFake: ["setTimeout", "nextTick"] + }); + }); +}); +test.use({ storageState: './e2e/test-data/VisualTestData_storage.json' }); + +test('Visual - Overlay Plot Loading Indicator @localstorage', async ({ page }) => { + // Go to baseURL + await page.goto('/', { waitUntil: 'networkidle' }); + + await page.locator('a:has-text("Unnamed Overlay Plot Overlay Plot")').click(); + //Ensure that we're on the Unnamed Overlay Plot object + await expect(page.locator('.l-browse-bar__object-name')).toContainText('Unnamed Overlay Plot'); + + //Wait for canvas to be rendered and stop animating + await page.locator('canvas >> nth=1').hover({trial: true}); + + //Take snapshot of Sine Wave Generator within Overlay Plot + await percySnapshot(page, 'SineWaveInOverlayPlot'); +}); diff --git a/e2e/tests/visual/generateVisualTestData.e2e.spec.js b/e2e/tests/visual/generateVisualTestData.e2e.spec.js new file mode 100644 index 0000000000..215c8b92e2 --- /dev/null +++ b/e2e/tests/visual/generateVisualTestData.e2e.spec.js @@ -0,0 +1,95 @@ +/***************************************************************************** + * Open MCT, Copyright (c) 2014-2022, 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 generating LocalStorage via Session Storage to be used +in some visual test suites like controlledClock.visual.spec.js. This suite should run to completion +and generate an artifact named ./e2e/test-data/VisualTestData_storage.json . This will run +on every Commit to ensure that this object still loads into tests correctly and will retain the +.e2e.spec.js suffix. + +TODO: Provide additional validation of object properties as it grows. + +*/ + +const { test } = require('../../fixtures.js'); +const { expect } = require('@playwright/test'); + +test('Generate Visual Test Data @localStorage', async ({ page, context }) => { + //Go to baseURL + await page.goto('/', { waitUntil: 'networkidle' }); + + await page.locator('button:has-text("Create")').click(); + + // add overlay plot with defaults + await page.locator('li:has-text("Overlay Plot")').click(); + + // Click on My Items in Tree. Workaround for https://github.com/nasa/openmct/issues/5184 + await page.click('form[name="mctForm"] a:has-text("My Items")'); + + await Promise.all([ + page.waitForNavigation(), + page.locator('text=OK').click(), + //Wait for Save Banner to appear1 + page.waitForSelector('.c-message-banner__message') + ]); + //Wait until Save Banner is gone + await page.locator('.c-message-banner__close-button').click(); + await page.waitForSelector('.c-message-banner__message', { state: 'detached'}); + + // save (exit edit mode) + await page.locator('text=Snapshot Save and Finish Editing Save and Continue Editing >> button').nth(1).click(); + await page.locator('text=Save and Finish Editing').click(); + + // click create button + await page.locator('button:has-text("Create")').click(); + + // add sine wave generator with defaults + await page.locator('li:has-text("Sine Wave Generator")').click(); + + //Add a 5000 ms Delay + await page.locator('[aria-label="Loading Delay \\(ms\\)"]').fill('5000'); + + // Click on My Items in Tree. Workaround for https://github.com/nasa/openmct/issues/5184 + await page.click('form[name="mctForm"] a:has-text("Overlay Plot")'); + + await Promise.all([ + page.waitForNavigation(), + page.locator('text=OK').click(), + //Wait for Save Banner to appear1 + page.waitForSelector('.c-message-banner__message') + ]); + //Wait until Save Banner is gone + await page.locator('.c-message-banner__close-button').click(); + await page.waitForSelector('.c-message-banner__message', { state: 'detached'}); + + // focus the overlay plot + await page.locator('text=Open MCT My Items >> span').nth(3).click(); + await Promise.all([ + page.waitForNavigation(), + page.locator('text=Unnamed Overlay Plot').first().click() + ]); + + await expect(page.locator('.l-browse-bar__object-name')).toContainText('Unnamed Overlay Plot'); + //Save localStorage for future test execution + await context.storageState({ path: './e2e/test-data/VisualTestData_storage.json' }); +}); diff --git a/example/generator/GeneratorProvider.js b/example/generator/GeneratorProvider.js index 2b845a1aae..ee0bf98f91 100644 --- a/example/generator/GeneratorProvider.js +++ b/example/generator/GeneratorProvider.js @@ -32,7 +32,8 @@ define([ offset: 0, dataRateInHz: 1, randomness: 0, - phase: 0 + phase: 0, + loadDelay: 0 }; function GeneratorProvider(openmct) { @@ -53,8 +54,9 @@ define([ 'period', 'offset', 'dataRateInHz', + 'randomness', 'phase', - 'randomness' + 'loadDelay' ]; request = request || {}; diff --git a/example/generator/generatorWorker.js b/example/generator/generatorWorker.js index 02807e06f2..bc9083da3a 100644 --- a/example/generator/generatorWorker.js +++ b/example/generator/generatorWorker.js @@ -116,6 +116,7 @@ var dataRateInHz = request.dataRateInHz; var phase = request.phase; var randomness = request.randomness; + var loadDelay = Math.max(request.loadDelay, 0); var step = 1000 / dataRateInHz; var nextStep = start - (start % step) + step; @@ -133,6 +134,14 @@ }); } + if (loadDelay === 0) { + postOnRequest(message, request, data); + } else { + setTimeout(() => postOnRequest(message, request, data), loadDelay); + } + } + + function postOnRequest(message, request, data) { self.postMessage({ id: message.id, data: request.spectra ? { diff --git a/example/generator/plugin.js b/example/generator/plugin.js index 953383aad9..21f5a40d9e 100644 --- a/example/generator/plugin.js +++ b/example/generator/plugin.js @@ -132,6 +132,17 @@ define([ "telemetry", "randomness" ] + }, + { + name: "Loading Delay (ms)", + control: "numberfield", + cssClass: "l-input-sm l-numeric", + key: "loadDelay", + required: true, + property: [ + "telemetry", + "loadDelay" + ] } ], initialize: function (object) { @@ -141,7 +152,8 @@ define([ offset: 0, dataRateInHz: 1, phase: 0, - randomness: 0 + randomness: 0, + loadDelay: 0 }; } }); diff --git a/src/api/forms/components/FormProperties.vue b/src/api/forms/components/FormProperties.vue index f6c5a62c8b..3c2af7b96d 100644 --- a/src/api/forms/components/FormProperties.vue +++ b/src/api/forms/components/FormProperties.vue @@ -60,6 +60,7 @@ tabindex="0" :disabled="isInvalid" class="c-button c-button--major" + aria-label="Save" @click="onSave" > {{ submitLabel }} @@ -67,6 +68,7 @@