diff --git a/.circleci/config.yml b/.circleci/config.yml index 3f7988be22..5a44b836c6 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -93,7 +93,7 @@ jobs: - generate_and_store_version_and_filesystem_artifacts e2e-test: parameters: - suite: #stable or full + suite: #ci or full type: string executor: pw-focal-development parallelism: 7 @@ -162,7 +162,7 @@ jobs: - run: npx playwright@1.45.2 install #Necessary for bare ubuntu machine - run: | export $(cat src/plugins/persistence/couch/.env.ci | xargs) - docker-compose -f src/plugins/persistence/couch/couchdb-compose.yaml up --detach + docker compose -f src/plugins/persistence/couch/couchdb-compose.yaml up --detach sleep 3 bash src/plugins/persistence/couch/setup-couchdb.sh - run: sh src/plugins/persistence/couch/replace-localstorage-with-couchdb-indexhtml.sh #Replace LocalStorage Plugin with CouchDB @@ -253,8 +253,8 @@ workflows: name: node18-chrome node-version: lts/hydrogen - e2e-test: - name: e2e-stable - suite: stable + name: e2e-ci + suite: ci - e2e-mobile - visual-a11y: name: visual-a11y-ci diff --git a/.cspell.json b/.cspell.json index cc3394b05e..3ba2caaac6 100644 --- a/.cspell.json +++ b/.cspell.json @@ -482,19 +482,10 @@ "composables", "countup", "darkmatter", - "Undeletes" - ], - "dictionaries": [ - "npm", - "softwareTerms", - "node", - "html", - "css", - "bash", - "en_US", - "en-gb", - "misc" + "Undeletes", + "SSSZ" ], + "dictionaries": ["npm", "softwareTerms", "node", "html", "css", "bash", "en_US", "en-gb", "misc"], "ignorePaths": [ "package.json", "dist/**", @@ -505,4 +496,4 @@ "html-test-results", "test-results" ] -} \ No newline at end of file +} diff --git a/.eslintrc.cjs b/.eslintrc.cjs index 8511e79a31..6607a1c553 100644 --- a/.eslintrc.cjs +++ b/.eslintrc.cjs @@ -5,9 +5,8 @@ const config = { browser: true, es2024: true, jasmine: true, - node: true, - worker: true, - serviceworker: true + amd: true, + node: true }, globals: { _: 'readonly', diff --git a/.github/workflows/e2e-couchdb.yml b/.github/workflows/e2e-couchdb.yml index 61ccdf6274..48ba6f352a 100644 --- a/.github/workflows/e2e-couchdb.yml +++ b/.github/workflows/e2e-couchdb.yml @@ -42,7 +42,7 @@ jobs: - name: Start CouchDB Docker Container and Init with Setup Scripts run: | export $(cat src/plugins/persistence/couch/.env.ci | xargs) - docker-compose -f src/plugins/persistence/couch/couchdb-compose.yaml up --detach + docker compose -f src/plugins/persistence/couch/couchdb-compose.yaml up --detach sleep 3 bash src/plugins/persistence/couch/setup-couchdb.sh bash src/plugins/persistence/couch/replace-localstorage-with-couchdb-indexhtml.sh diff --git a/.github/workflows/e2e-flakefinder.yml b/.github/workflows/e2e-flakefinder.yml index 4688e5d18a..c7eccf222d 100644 --- a/.github/workflows/e2e-flakefinder.yml +++ b/.github/workflows/e2e-flakefinder.yml @@ -34,7 +34,7 @@ jobs: - run: npm ci --no-audit --progress=false - name: Run E2E Tests (Repeated 10 Times) - run: npm run test:e2e:stable -- --retries=0 --repeat-each=10 --max-failures=50 + run: npm run test:e2e:ci -- --retries=0 --repeat-each=10 --max-failures=50 - name: Archive test results if: success() || failure() diff --git a/README.md b/README.md index 054c73722f..af09dbf132 100644 --- a/README.md +++ b/README.md @@ -109,7 +109,7 @@ Our e2e (end-to-end), Visual, and Performance tests leverage the Playwright fram - **e2e Tests**: These tests are run on every commit. To run the tests locally, use: ```sh - npm run test:e2e:stable + npm run test:e2e:ci ``` - **Visual Tests**: For running the visual test suite, use: diff --git a/TESTING.md b/TESTING.md index ba8f9cc8a3..24358c6ae3 100644 --- a/TESTING.md +++ b/TESTING.md @@ -66,8 +66,8 @@ The e2e line coverage is a bit more complex than the karma implementation. This 1. Each e2e suite will start webpack with the ```npm run start:coverage``` command with config `webpack.coverage.mjs` and the `babel-plugin-istanbul` plugin to generate code coverage during e2e test execution using our custom [baseFixture](./baseFixtures.js). 1. During testcase execution, each e2e shard will generate its piece of the larger coverage suite. **This coverage file is not merged**. The raw coverage file is stored in a `.nyc_report` directory. 1. [nyc](https://github.com/istanbuljs/nyc) converts this directory into a `lcov` file with the following command `npm run cov:e2e:report` -1. Most of the tests are run in the '@stable' configuration and focus on chrome/ubuntu at a single resolution. This coverage is published to codecov with `npm run cov:e2e:stable:publish`. -1. The rest of our coverage only appears when run against `@unstable` tests, persistent datastore (couchdb), non-ubuntu machines, and non-chrome browsers with the `npm run cov:e2e:full:publish` flag. Since this happens about once a day, we have leveraged codecov.io's carryforward flag to report on lines covered outside of each commit on an individual PR. +1. Most of the tests focus on chrome/ubuntu at a single resolution. This coverage is published to codecov with `npm run cov:e2e:ci:publish`. +1. The rest of our coverage only appears when run against persistent datastore (couchdb), non-ubuntu machines, and non-chrome browsers with the `npm run cov:e2e:full:publish` flag. Since this happens about once a day, we have leveraged codecov.io's carryforward flag to report on lines covered outside of each commit on an individual PR. ### Limitations in our code coverage reporting diff --git a/codecov.yml b/codecov.yml index 07da9fa4c3..aab53fc30c 100644 --- a/codecov.yml +++ b/codecov.yml @@ -11,18 +11,18 @@ coverage: informational: true precision: 2 round: down - range: '66...100' + range: "66...100" flags: unit: carryforward: false - e2e-stable: + e2e-ci: carryforward: false e2e-full: carryforward: true comment: - layout: 'diff,flags,files,footer' + layout: "diff,flags,files,footer" behavior: default require_changes: false show_carryforward_flags: true diff --git a/e2e/.eslintrc.cjs b/e2e/.eslintrc.cjs index 9d378f77c0..a141c4bf2f 100644 --- a/e2e/.eslintrc.cjs +++ b/e2e/.eslintrc.cjs @@ -1,14 +1,24 @@ /* eslint-disable no-undef */ module.exports = { - extends: ['plugin:playwright/playwright-test'], + extends: ['plugin:playwright/recommended'], rules: { - 'playwright/max-nested-describe': ['error', { max: 1 }] + 'playwright/max-nested-describe': ['error', { max: 1 }], + 'playwright/expect-expect': 'off' }, overrides: [ { - files: ['tests/visual/*.spec.js'], + //Apply Best Practices to externalFixtures and exampleTemplate.e2e.spec.js + files: [ + 'appActions.js', + 'baseFixtures.js', + 'pluginFixtures.js', + '**/exampleTemplate.e2e.spec.js' + ], rules: { - 'playwright/no-wait-for-timeout': 'off' + 'playwright/no-raw-locators': 'error', + 'playwright/no-nth-methods': 'error', + 'playwright/no-get-by-title': 'error', + 'playwright/prefer-comparison-matcher': 'error' } } ] diff --git a/e2e/README.md b/e2e/README.md index f52a5d4131..b10956ba44 100644 --- a/e2e/README.md +++ b/e2e/README.md @@ -225,14 +225,13 @@ Current list of test tags: |:-:|-| |`@mobile` | Test case or test suite is compatible with Playwright's iPad support and Open MCT's read-only mobile view (i.e. no create button).| |`@a11y` | Test case or test suite to execute playwright-axe accessibility checks and generate a11y reports.| -|`@gds` | Denotes a GDS Test Case used in the VIPER Mission.| |`@addInit` | Initializes the browser with an injected and artificial state. Useful for loading non-default plugins. Likely will not work outside of `npm start`.| |`@localStorage` | Captures or generates session storage to manipulate browser state. Useful for excluding in tests which require a persistent backend (i.e. CouchDB). See [note](#utilizing-localstorage)| |`@snapshot` | Uses Playwright's snapshot functionality to record a copy of the DOM for direct comparison. Must be run inside of the playwright container.| -|`@unstable` | A new test or test which is known to be flaky.| |`@2p` | Indicates that multiple users are involved, or multiple tabs/pages are used. Useful for testing multi-user interactivity.| |`@generatedata` | Indicates that a test is used to generate testdata or test the generated test data. Usually to be associated with localstorage, but this may grow over time.| |`@clock` | A test which modifies the clock. These have expanded out of the visual tests and into the functional tests. +|`@framework` | A test for open mct e2e capabilities. This is primarily to ensure we don't break projects which depend on sourcing this project's fixtures like appActions.js. ### Continuous Integration @@ -248,7 +247,7 @@ Our CI environment consists of 3 main modes of operation: CircleCI -- Stable e2e tests against ubuntu and chrome +- e2e tests against ubuntu and chrome - Performance tests against ubuntu and chrome - e2e tests are linted - Visual and a11y tests are run in a single resolution on the default `espresso` theme @@ -287,18 +286,6 @@ So for every commit, Playwright is effectively running 4 x 2 concurrent browserc At the same time, we don't want to waste CI resources on parallel runs, so we've configured each shard to fail after 5 test failures. Test failure logs are recorded and stored to allow fast triage. -#### Test Promotion - -In order to maintain fast and reliable feedback, tests go through a promotion process. All new test cases or test suites must be labeled with the `@unstable` annotation. The Open MCT dev team runs these unstable tests in our private repos to ensure they work downstream and are reliable. - -- To run the stable tests, use the `npm run test:e2e:stable` command. -- To run the new and flaky tests, use the `npm run test:e2e:unstable` command. - -A testcase and testsuite are to be unmarked as @unstable when: - -1. They run as part of "full" run 5 times without failure. -2. They've been by a Open MCT Developer 5 times in the closed source repo without failure. - ### Cross-browser and Cross-operating system #### **What's supported:** @@ -380,8 +367,7 @@ By adhering to this principle, we can create tests that are both robust and refl 1. Avoid creating locator aliases. This likely means that you're compensating for a bad locator. Improve the application instead. 1. Leverage `await page.goto('./', { waitUntil: 'domcontentloaded' });` instead of `{ waitUntil: 'networkidle' }`. Tests run against deployments with websockets often have issues with the networkidle detection. -#### How to make tests faster and more resilient - +#### How to make tests faster and more resilient to application changes 1. Avoid app interaction when possible. The best way of doing this is to navigate directly by URL: ```js @@ -396,6 +382,16 @@ By adhering to this principle, we can create tests that are both robust and refl - Initial navigation should _almost_ always use the `{ waitUntil: 'domcontentloaded' }` option. 1. Avoid repeated setup to test a single assertion. Write longer tests with multiple soft assertions. This ensures that your changes will be picked up with large refactors. + 1. Use [user-facing locators](https://playwright.dev/docs/best-practices#use-locators) (Now a eslint rule!) + + ```js + page.getByRole('button', { name: 'Create' } ) + ``` + Instead of + ```js + page.locator('.c-create-button') + ``` + Note: `page.locator()` can be used in performance tests as xk6-browser does not yet support the new `page.getBy` pattern and css lookups can be [1.5x faster](https://serpapi.com/blog/css-selectors-faster-than-getbyrole-playwright/) ##### Utilizing LocalStorage @@ -448,6 +444,7 @@ By adhering to this principle, we can create tests that are both robust and refl - Use Open MCT's fixed-time mode unless explicitly testing realtime clock - Employ the `createExampleTelemetryObject` appAction to source telemetry and specify a `name` to avoid autogenerated names. - Avoid creating objects with a time component like timers and clocks. +- Utilize the playwright clock() API. See @clock Annotations for examples. 5. **Hide the Tree and Inspector**: Generally, your test will not require comparisons involving the tree and inspector. These aspects are covered in component-specific tests (explained below). To exclude them from the comparison by default, navigate to the root of the main view with the tree and inspector hidden: - `await page.goto('./#/browse/mine?hideTree=true&hideInspector=true')` @@ -493,29 +490,25 @@ For best practices with regards to mocking network responses, see our [couchdb.e The following contains a list of tips and tricks which don't exactly fit into a FAQ or Best Practices doc. - (Advanced) Overriding the Browser's Clock -It is possible to override the browser's clock in order to control time-based elements. Since this can cause unwanted behavior (i.e. Tree not rendering), only use this sparingly. To do this, use the `overrideClock` fixture as such: +It is possible to override the browser's clock in order to control time-based elements. Since this can cause unwanted behavior -- i.e. Tree not rendering -- only use this sparingly. Use the `page.clock()` API as such: ```js import { test, expect } from '../../pluginFixtures.js'; -test.describe('foo test suite', () => { - - // All subsequent tests in this suite will override the clock - test.use({ - clockOptions: { - now: 1732413600000, // A timestamp given as milliseconds since the epoch - shouldAdvanceTime: true // Should the clock tick? - } +test.describe('foo test suite @clock', () => { + test.beforeEach(async ({ page }) => { + //Set clock time + await page.clock.install({ time: MISSION_TIME }); + await page.clock.resume(); + //Navigate to page with new clock + await page.goto('./', { waitUntil: 'domcontentloaded' }); }); - test('bar test', async ({ page }) => { - // ... + test('bar here', async ({ page }) => { + /// ... }); -}); ``` - More info and options for `overrideClock` can be found in [baseFixtures.js](baseFixtures.js) - - Working with multiple pages There are instances where multiple browser pages will needed to verify multi-page or multi-tab application behavior. Make sure to use the `@2p` annotation as well as name each page appropriately: i.e. `page1` and `page2` or `tab1` and `tab2` depending on the intended use case. Generally pages should be used unless testing `sharedWorker` code, specifically. diff --git a/e2e/appActions.js b/e2e/appActions.js index 35b238843e..f1602bc3cd 100644 --- a/e2e/appActions.js +++ b/e2e/appActions.js @@ -35,7 +35,6 @@ * @property {string} type the type of domain object to create (e.g.: "Sine Wave Generator"). * @property {string} [name] the desired name of the created domain object. * @property {string | import('../src/api/objects/ObjectAPI').Identifier} [parent] the Identifier or uuid of the parent object. - * @property {Record} [customParameters] any additional parameters to be passed to the domain object's form. E.g. '[aria-label="Data Rate (hz)"]': {'0.1'} */ /** @@ -62,14 +61,14 @@ import { v4 as genUuid } from 'uuid'; * This common function creates a domain object with the default options. It is the preferred way of creating objects * in the e2e suite when uninterested in properties of the objects themselves. * - * @param {import('@playwright/test').Page} page - * @param {CreateObjectOptions} options + * @param {import('@playwright/test').Page} page - The Playwright page object. + * @param {Object} options - Options for creating the domain object. + * @param {string} options.type - The type of domain object to create (e.g., "Sine Wave Generator"). + * @param {string} [options.name] - The desired name of the created domain object. + * @param {string | import('../src/api/objects/ObjectAPI').Identifier} [options.parent='mine'] - The Identifier or uuid of the parent object. Defaults to 'mine' folder * @returns {Promise} An object containing information about the newly created domain object. */ -async function createDomainObjectWithDefaults( - page, - { type, name, parent = 'mine', customParameters = {} } -) { +async function createDomainObjectWithDefaults(page, { type, name, parent = 'mine' }) { if (!name) { name = `${type}:${genUuid()}`; } @@ -86,32 +85,18 @@ async function createDomainObjectWithDefaults( // Click the object specified by 'type'-- case insensitive await page.getByRole('menuitem', { name: new RegExp(`^${type}$`, 'i') }).click(); - // Modify the name input field of the domain object to accept 'name' - const nameInput = page.locator('form[name="mctForm"] .first input[type="text"]'); - await nameInput.fill(''); - await nameInput.fill(name); + // Fill in the name of the object + await page.getByLabel('Title', { exact: true }).fill(''); + await page.getByLabel('Title', { exact: true }).fill(name); if (page.testNotes) { // Fill the "Notes" section with information about the // currently running test and its project. - const notesInput = page.locator('form[name="mctForm"] #notes-textarea'); - await notesInput.fill(page.testNotes); + // eslint-disable-next-line playwright/no-raw-locators + await page.locator('#notes-textarea').fill(page.testNotes); } - // If there are any further parameters, fill them in - for (const [key, value] of Object.entries(customParameters)) { - const input = page.locator(`form[name="mctForm"] ${key}`); - await input.fill(''); - await input.fill(value); - } - - // Click OK button and wait for Navigate event - await Promise.all([ - page.waitForLoadState(), - await page.getByRole('button', { name: 'Save' }).click(), - // Wait for Save Banner to appear - page.waitForSelector('.c-message-banner__message') - ]); + await page.getByRole('button', { name: 'Save' }).click(); // Wait until the URL is updated await page.waitForURL(`**/${parent}/*`); @@ -151,61 +136,41 @@ async function createNotification(page, createNotificationOptions) { } /** - * Expand an item in the tree by a given object name. + * Create a Plan object from JSON with the provided options. Must be used with a json based plan. + * Please check appActions.e2e.spec.js for an example of how to use this function. + * * @param {import('@playwright/test').Page} page * @param {string} name - */ -async function expandTreePaneItemByName(page, name) { - const treePane = page.getByRole('tree', { - name: 'Main Tree' - }); - const treeItem = treePane.locator(`role=treeitem[expanded=false][name=/${name}/]`); - const expandTriangle = treeItem.locator('.c-disclosure-triangle'); - await expandTriangle.click(); -} - -/** - * Create a Plan object from JSON with the provided options. - * @param {import('@playwright/test').Page} page - * @param {*} options + * @param {Object} json + * @param {string | import('../src/api/objects/ObjectAPI').Identifier} [parent] the uuid or identifier of the parent object. Defaults to 'mine' * @returns {Promise} An object containing information about the newly created domain object. */ async function createPlanFromJSON(page, { name, json, parent = 'mine' }) { - if (!name) { - name = `Plan:${genUuid()}`; - } - const parentUrl = await getHashUrlToDomainObject(page, parent); // Navigate to the parent object. This is necessary to create the object // in the correct location, such as a folder, layout, or plot. await page.goto(`${parentUrl}`); - // Click the Create button await page.getByRole('button', { name: 'Create' }).click(); - // Click 'Plan' menu option - await page.click(`li:text("Plan")`); + await page.getByRole('menuitem', { name: 'Plan' }).click(); - // Modify the name input field of the domain object to accept 'name' - const nameInput = page.getByLabel('Title', { exact: true }); - await nameInput.fill(''); - await nameInput.fill(name); + // Fill in the name of the object or generate a random one + if (!name) { + name = `Plan:${genUuid()}`; + } + await page.getByLabel('Title', { exact: true }).fill(''); + await page.getByLabel('Title', { exact: true }).fill(name); // Upload buffer from memory - await page.locator('input#fileElem').setInputFiles({ + await page.getByLabel('Select File...').setInputFiles({ name: 'plan.txt', mimeType: 'text/plain', buffer: Buffer.from(JSON.stringify(json)) }); - // Click OK button and wait for Navigate event - await Promise.all([ - page.waitForLoadState(), - page.click('[aria-label="Save"]'), - // Wait for Save Banner to appear - page.waitForSelector('.c-message-banner__message') - ]); + await page.getByLabel('Save').click(); // Wait until the URL is updated await page.waitForURL(`**/${parent}/*`); @@ -233,10 +198,10 @@ async function createExampleTelemetryObject(page, parent = 'mine') { await page.getByRole('button', { name: 'Create' }).click(); - await page.locator('li:has-text("Sine Wave Generator")').click(); + await page.getByRole('menuitem', { name: 'Sine Wave Generator' }).click(); const name = 'VIPER Rover Heading'; - await page.getByRole('dialog').locator('input[type="text"]').fill(name); + await page.getByLabel('Title', { exact: true }).fill(name); // Fill out the fields with default values await page.getByRole('spinbutton', { name: 'Period' }).fill('10'); @@ -263,7 +228,9 @@ async function createExampleTelemetryObject(page, parent = 'mine') { } /** - * Navigates directly to a given object url, in fixed time mode, with the given start and end bounds. + * Navigates directly to a given object url, in fixed time mode, with the given start and end bounds. Note: does not set + * default view type. + * * @param {import('@playwright/test').Page} page * @param {string} url The url to the domainObject * @param {string | number} start The starting time bound in milliseconds since epoch @@ -276,9 +243,13 @@ async function navigateToObjectWithFixedTimeBounds(page, url, start, end) { } /** - * Navigates directly to a given object url, in real-time mode. + * Navigates directly to a given object url, in real-time mode. Note: does not set + * default view type. + * * @param {import('@playwright/test').Page} page * @param {string} url The url to the domainObject + * @param {string | number} start The start offset in milliseconds + * @param {string | number} end The end offset in milliseconds */ async function navigateToObjectWithRealTime(page, url, start = '1800000', end = '30000') { await page.goto( @@ -287,23 +258,11 @@ async function navigateToObjectWithRealTime(page, url, start = '1800000', end = } /** - * Open the given `domainObject`'s context menu from the object tree. - * Expands the path to the object and scrolls to it if necessary. + * Expands the entire object tree (every expandable tree item). Can be used to + * ensure that the tree is fully expanded before performing actions on objects. + * Can be applied to either the main tree or the create modal tree. * * @param {import('@playwright/test').Page} page - * @param {string} url the url to the object - */ -async function openObjectTreeContextMenu(page, url) { - await page.goto(url); - await page.getByLabel('Show selected item in tree').click(); - await page.locator('.is-navigated-object').click({ - button: 'right' - }); -} - -/** - * Expands the entire object tree (every expandable tree item). - * @param {import('@playwright/test').Page} page * @param {"Main Tree" | "Create Modal Tree"} [treeName="Main Tree"] */ async function expandEntireTree(page, treeName = 'Main Tree') { @@ -314,9 +273,10 @@ async function expandEntireTree(page, treeName = 'Main Tree') { .getByRole('treeitem', { expanded: false }) - .locator('span.c-disclosure-triangle.is-enabled'); + .getByLabel(/Expand/); while ((await collapsedTreeItems.count()) > 0) { + //eslint-disable-next-line playwright/no-nth-methods await collapsedTreeItems.nth(0).click(); // FIXME: Replace hard wait with something event-driven. @@ -388,10 +348,11 @@ async function _isInEditMode(page, identifier) { /** * Set the time conductor mode to either fixed timespan or realtime mode. + * @private * @param {import('@playwright/test').Page} page * @param {boolean} [isFixedTimespan=true] true for fixed timespan mode, false for realtime mode; default is true */ -async function setTimeConductorMode(page, isFixedTimespan = true) { +async function _setTimeConductorMode(page, isFixedTimespan = true) { // Click 'mode' button await page.getByRole('button', { name: 'Time Conductor Mode', exact: true }).click(); await page.getByRole('button', { name: 'Time Conductor Mode Menu' }).click(); @@ -412,7 +373,7 @@ async function setTimeConductorMode(page, isFixedTimespan = true) { * @param {import('@playwright/test').Page} page */ async function setFixedTimeMode(page) { - await setTimeConductorMode(page, true); + await _setTimeConductorMode(page, true); } /** @@ -420,7 +381,7 @@ async function setFixedTimeMode(page) { * @param {import('@playwright/test').Page} page */ async function setRealTimeMode(page) { - await setTimeConductorMode(page, false); + await _setTimeConductorMode(page, false); } /** @@ -542,19 +503,20 @@ async function setTimeConductorBounds(page, { submitChanges = true, ...bounds }) } /** - * Set the independent time conductor bounds in fixed time mode + * Set the bounds of the visible conductor in fixed time mode. + * Requires that page already has an independent time conductor in view. * @param {import('@playwright/test').Page} page - * @param {string} startDate - * @param {string} endDate + * @param {string} start - The start date in 'YYYY-MM-DD HH:mm:ss.SSSZ' format + * @param {string} end - The end date in 'YYYY-MM-DD HH:mm:ss.SSSZ' format */ -async function setIndependentTimeConductorBounds(page, { start, end }) { +async function setFixedIndependentTimeConductorBounds(page, { start, end }) { // Activate Independent Time Conductor await page.getByLabel('Enable Independent Time Conductor').click(); // Bring up the time conductor popup await page.getByLabel('Independent Time Conductor Settings').click(); - await expect(page.locator('.itc-popout')).toBeInViewport(); - await setTimeBounds(page, start, end); + await expect(page.getByLabel('Time Conductor Options')).toBeInViewport(); + await _setTimeBounds(page, start, end); await page.keyboard.press('Enter'); } @@ -563,10 +525,10 @@ async function setIndependentTimeConductorBounds(page, { start, end }) { * Set the bounds of the visible conductor in fixed time mode * @private * @param {import('@playwright/test').Page} page - * @param {string} startDate - * @param {string} endDate + * @param {string} start - The start date in 'YYYY-MM-DD HH:mm:ss.SSSZ' format + * @param {string} end - The end date in 'YYYY-MM-DD HH:mm:ss.SSSZ' format */ -async function setTimeBounds(page, startDate, endDate) { +async function _setTimeBounds(page, startDate, endDate) { if (startDate) { // Fill start time await page @@ -596,11 +558,13 @@ async function setTimeBounds(page, startDate, endDate) { * all plots on the page and waits up to the default timeout for the class to be * attached to each plot. * @param {import('@playwright/test').Page} page + * @param {number} [timeout] Provide a custom timeout in milliseconds to override the default timeout */ -async function waitForPlotsToRender(page) { +async function waitForPlotsToRender(page, { timeout } = {}) { + //eslint-disable-next-line playwright/no-raw-locators const plotLocator = page.locator('.gl-plot'); for (const plot of await plotLocator.all()) { - await expect(plot).toHaveClass(/js-series-data-loaded/); + await expect(plot).toHaveClass(/js-series-data-loaded/, { timeout }); } } @@ -665,41 +629,20 @@ async function getCanvasPixels(page, canvasSelector) { ); } -/** - * @param {import('@playwright/test').Page} page - * @param {string} myItemsFolderName - * @param {string} url - * @param {string} newName - */ -async function renameObjectFromContextMenu(page, url, newName) { - await openObjectTreeContextMenu(page, url); - await page.click('li:text("Edit Properties")'); - const nameInput = page.getByLabel('Title', { exact: true }); - await nameInput.fill(''); - await nameInput.fill(newName); - await page.click('[aria-label="Save"]'); -} - export { createDomainObjectWithDefaults, createExampleTelemetryObject, createNotification, createPlanFromJSON, expandEntireTree, - expandTreePaneItemByName, getCanvasPixels, - getFocusedObjectUuid, - getHashUrlToDomainObject, navigateToObjectWithFixedTimeBounds, navigateToObjectWithRealTime, - openObjectTreeContextMenu, - renameObjectFromContextMenu, setEndOffset, + setFixedIndependentTimeConductorBounds, setFixedTimeMode, - setIndependentTimeConductorBounds, setRealTimeMode, setStartOffset, setTimeConductorBounds, - setTimeConductorMode, waitForPlotsToRender }; diff --git a/e2e/baseFixtures.js b/e2e/baseFixtures.js index 5cfb2678e4..6d0662a875 100644 --- a/e2e/baseFixtures.js +++ b/e2e/baseFixtures.js @@ -30,7 +30,6 @@ import { expect, request, test } from '@playwright/test'; import fs from 'fs'; import path from 'path'; -import sinon from 'sinon'; import { fileURLToPath } from 'url'; import { v4 as uuid } from 'uuid'; @@ -70,82 +69,6 @@ const extendedTest = test.extend({ */ coveragePath: [istanbulCLIOutput, { option: true }], - /** - * This allows the test to manipulate the browser clock. This is useful for Visual and Snapshot tests which need - * the Time Indicator Clock to be in a specific state. - * - * Warning: Has many limitations and secondary side effects in Open MCT. - * 1. The tree component does not render. - * 2. page.WaitForNavigation does not trigger. - * - * Usage: - * ```js - * test.use({ - * clockOptions: { - * now: MISSION_TIME, - * shouldAdvanceTime: true - * ``` - * If clockOptions are provided, will override the default clock with fake timers provided by SinonJS. - * - * Default: `undefined` - * - * @see {@link https://github.com/microsoft/playwright/issues/6347 Github RFE} - * @see {@link https://github.com/sinonjs/fake-timers/#var-clock--faketimersinstallconfig SinonJS FakeTimers Config} - * @type {import('@types/sinonjs__fake-timers').FakeTimerInstallOpts} - */ - clockOptions: [undefined, { option: true }], - overrideClock: [ - async ({ context, clockOptions }, use) => { - if (clockOptions !== undefined) { - await context.addInitScript({ - path: fileURLToPath(new URL('../node_modules/sinon/pkg/sinon.js', import.meta.url)) - }); - await context.addInitScript((options) => { - window.__clock = sinon.useFakeTimers(options); - }, clockOptions); - } - - await use(context); - }, - { - auto: true, - scope: 'test' - } - ], - /** - * Exposes a function to manually tick the clock. This is useful when overriding the clock to not - * tick (`shouldAdvanceTime: false`) for visual tests, as events such as re-renders and router params - * updates are clock-driven and must be manually ticked. - * - * Usage: - * ```js - * test.describe('Manual Clock Tick', () => { - * test.use({ - * clockOptions: { - * now: MISSION_TIME, // Set to the desired time - * shouldAdvanceTime: false // Clock overridden to no longer tick - * } - * }); - * test('Visual - Manual Clock Tick', async ({ page, tick }) => { - * // Tick the clock 2 seconds in the future - * await tick(2000); - * }); - * }); - * ``` - * - * @param {Object} param0 - * @param {import('@playwright/test').Page} param0.page - * @param {import('@playwright/test').Use} param0.use - */ - tick: async ({ page }, use) => { - // eslint-disable-next-line func-style - const tick = async (milliseconds) => { - await page.evaluate((_milliseconds) => { - window.__clock.tick(_milliseconds); - }, milliseconds); - }; - await use(tick); - }, /** * Extends the base context class to add codecoverage shim. * @see {@link https://github.com/mxschmitt/playwright-test-coverage Github Project} @@ -184,20 +107,7 @@ const extendedTest = test.extend({ * Extends the base page class to enable console log error detection. * @see {@link https://github.com/microsoft/playwright/discussions/11690 Github Discussion} */ - page: async ({ page, failOnConsoleError, clockOptions }, use) => { - // If overriding the clock, we must also override the Date.now() - // function in the generatorWorker context. This is necessary - // to ensure that example telemetry data is generated for the new clock time. - if (clockOptions?.now !== undefined) { - page.on('worker', (worker) => { - if (worker.url().includes('generatorWorker')) { - worker.evaluate((time) => { - self.Date.now = () => time; - }, clockOptions.now); - } - }); - } - + page: async ({ page, failOnConsoleError }, use) => { // Capture any console errors during test execution const messages = []; page.on('console', (msg) => messages.push(msg)); @@ -207,28 +117,12 @@ const extendedTest = test.extend({ // Assert against console errors during teardown if (failOnConsoleError) { messages.forEach((msg) => + // eslint-disable-next-line playwright/no-standalone-expect expect .soft(msg.type(), `Console error detected: ${_consoleMessageToString(msg)}`) .not.toEqual('error') ); } - }, - /** - * Extends the base browser class to enable CDP connection definition in playwright.config.js. Once - * that RFE is implemented, this function can be removed. - * @see {@link https://github.com/microsoft/playwright/issues/8379 Github RFE} - */ - browser: async ({ playwright, browser }, use, workerInfo) => { - // Use browserless if configured - if (workerInfo.project.name.match(/browserless/)) { - const vBrowser = await playwright.chromium.connectOverCDP({ - endpointURL: 'ws://localhost:3003' - }); - await use(vBrowser); - } else { - // Use Local Browser for testing. - await use(browser); - } } }); diff --git a/e2e/helper/faultUtils.js b/e2e/helper/faultUtils.js index 54c30e23c5..05c7df98db 100644 --- a/e2e/helper/faultUtils.js +++ b/e2e/helper/faultUtils.js @@ -59,8 +59,9 @@ export async function navigateToFaultManagementWithoutExample(page) { /** * @param {import('@playwright/test').Page} page */ -export async function navigateToFaultItemInTree(page) { - await page.goto('./', { waitUntil: 'networkidle' }); +async function navigateToFaultItemInTree(page) { + await page.goto('./', { waitUntil: 'domcontentloaded' }); + await page.waitForURL('**/#/browse/mine?**'); const faultManagementTreeItem = page .getByRole('tree', { diff --git a/e2e/helper/notebookUtils.js b/e2e/helper/notebookUtils.js index acf13b6ad2..07682c4ae5 100644 --- a/e2e/helper/notebookUtils.js +++ b/e2e/helper/notebookUtils.js @@ -63,7 +63,7 @@ async function dragAndDropEmbed(page, notebookObject) { // Expand the tree to reveal the notebook await page.getByLabel('Show selected item in tree').click(); // Drag and drop the SWG into the notebook - await page.dragAndDrop(`text=${swg.name}`, NOTEBOOK_DROP_AREA); + await page.getByLabel(`Navigate to ${swg.name}`).dragTo(page.locator(NOTEBOOK_DROP_AREA)); await commitEntry(page); } @@ -84,6 +84,7 @@ async function startAndAddRestrictedNotebookObject(page) { path: fileURLToPath(new URL('./addInitRestrictedNotebook.js', import.meta.url)) }); await page.goto('./', { waitUntil: 'domcontentloaded' }); + await page.waitForURL('**/browse/mine?**'); return createDomainObjectWithDefaults(page, { type: CUSTOM_NAME, @@ -95,10 +96,9 @@ async function startAndAddRestrictedNotebookObject(page) { * @param {import('@playwright/test').Page} page */ async function lockPage(page) { - const commitButton = page.locator('button:has-text("Commit Entries")'); - await commitButton.click(); - - //Wait until Lock Banner is visible + // Click the Commit Entries button + await page.getByLabel('Commit Entries').click(); + // Wait until Lock Banner is visible await page.locator('text=Lock Page').click(); } diff --git a/e2e/helper/planningUtils.js b/e2e/helper/planningUtils.js index 9150dd7565..43988532f3 100644 --- a/e2e/helper/planningUtils.js +++ b/e2e/helper/planningUtils.js @@ -30,9 +30,9 @@ import { expect } from '../pluginFixtures.js'; * start time as the start bound and the current activity's end time as the end bound. * @param {import('@playwright/test').Page} page the page * @param {Object} plan The raw plan json to assert against - * @param {string} objectUrl The URL of the object to assert against (plan or gantt chart) + * @param {string} planObjectUrl The URL of the object to assert against (plan or gantt chart) */ -export async function assertPlanActivities(page, plan, objectUrl) { +export async function assertPlanActivities(page, plan, planObjectUrl) { const groups = Object.keys(plan); for (const group of groups) { for (let i = 0; i < plan[group].length; i++) { @@ -48,13 +48,12 @@ export async function assertPlanActivities(page, plan, objectUrl) { // Switch to fixed time mode with all plan events within the bounds await page.goto( - `${objectUrl}?tc.mode=fixed&tc.startBound=${startBound}&tc.endBound=${endBound}&tc.timeSystem=utc&view=plan.view` + `${planObjectUrl}?tc.mode=fixed&tc.startBound=${startBound}&tc.endBound=${endBound}&tc.timeSystem=utc&view=plan.view` ); // Assert that the number of activities in the plan view matches the number of // activities in the plan data within the specified time bounds - const eventCount = await page.locator('.activity-bounds').count(); - expect(eventCount).toEqual( + await expect(page.locator('.activity-bounds')).toHaveCount( Object.values(plan) .flat() .filter((event) => @@ -101,8 +100,8 @@ export async function assertPlanOrderedSwimLanes(page, plan, objectUrl) { for (let i = 0; i < groups.length; i++) { // Assert that the order of groups in the plan view matches the order of // groups in the plan data - const groupName = await planGroups[i].innerText(); - expect(groupName).toEqual(groups[i].name); + const groupName = planGroups[i]; + await expect(groupName).toHaveText(groups[i].name); } } diff --git a/e2e/package.json b/e2e/package.json index 3b1343d0e5..af4cbafafc 100644 --- a/e2e/package.json +++ b/e2e/package.json @@ -14,16 +14,14 @@ "test:visual": "percy exec" }, "devDependencies": { - "@types/sinonjs__fake-timers": "8.1.5", "@percy/cli": "1.27.4", "@percy/playwright": "1.0.4", "@playwright/test": "1.45.2", - "@axe-core/playwright": "4.8.5", - "sinon": "17.0.0" + "@axe-core/playwright": "4.8.5" }, "author": { "name": "National Aeronautics and Space Administration", "url": "https://www.nasa.gov" }, "license": "Apache-2.0" -} \ No newline at end of file +} diff --git a/e2e/test-data/display_layout_with_child_layouts.json b/e2e/test-data/display_layout_with_child_layouts.json index 899a0e31b3..f19c38d484 100644 --- a/e2e/test-data/display_layout_with_child_layouts.json +++ b/e2e/test-data/display_layout_with_child_layouts.json @@ -6,15 +6,15 @@ "localStorage": [ { "name": "mct", - "value": "{\"mine\":{\"identifier\":{\"key\":\"mine\",\"namespace\":\"\"},\"name\":\"My Items\",\"type\":\"folder\",\"composition\":[{\"key\":\"80d08d10-2f2b-4ebb-847c-2385016718e7\",\"namespace\":\"\"}],\"location\":\"ROOT\",\"modified\":1732413601880,\"created\":1732413600900,\"persisted\":1732413601880},\"80d08d10-2f2b-4ebb-847c-2385016718e7\":{\"identifier\":{\"key\":\"80d08d10-2f2b-4ebb-847c-2385016718e7\",\"namespace\":\"\"},\"name\":\"Parent Display Layout\",\"type\":\"layout\",\"composition\":[{\"key\":\"cdff8398-81e5-4a37-8c2d-8ec0a0ed7d16\",\"namespace\":\"\"},{\"key\":\"62ac54e8-e424-41b3-889c-83f36c13676d\",\"namespace\":\"\"}],\"configuration\":{\"items\":[{\"width\":32,\"height\":18,\"x\":1,\"y\":30,\"identifier\":{\"key\":\"cdff8398-81e5-4a37-8c2d-8ec0a0ed7d16\",\"namespace\":\"\"},\"hasFrame\":true,\"fontSize\":\"default\",\"font\":\"default\",\"type\":\"subobject-view\",\"id\":\"a71236dd-9178-4b96-88d9-b93ceee12f32\"},{\"width\":32,\"height\":18,\"x\":30,\"y\":1,\"identifier\":{\"key\":\"62ac54e8-e424-41b3-889c-83f36c13676d\",\"namespace\":\"\"},\"hasFrame\":true,\"fontSize\":\"default\",\"font\":\"default\",\"type\":\"subobject-view\",\"id\":\"b512cc87-9291-4b55-b9a5-4e5641bc8b83\"}],\"layoutGrid\":[10,10]},\"notes\":\"framework/generateLocalStorageData.e2e.spec.js\\nGenerate Visual Test Data @localStorage @generatedata\\nGenerate display layout with 2 child display layouts\\nchrome\",\"modified\":1732413605020,\"location\":\"mine\",\"created\":1732413601880,\"persisted\":1732413605020},\"cdff8398-81e5-4a37-8c2d-8ec0a0ed7d16\":{\"name\":\"Child Layout 1\",\"type\":\"layout\",\"identifier\":{\"key\":\"cdff8398-81e5-4a37-8c2d-8ec0a0ed7d16\",\"namespace\":\"\"},\"composition\":[],\"configuration\":{\"items\":[],\"layoutGrid\":[10,10]},\"notes\":\"framework/generateLocalStorageData.e2e.spec.js\\nGenerate Visual Test Data @localStorage @generatedata\\nGenerate display layout with 2 child display layouts\\nchrome\",\"modified\":1732413602980,\"location\":\"80d08d10-2f2b-4ebb-847c-2385016718e7\",\"created\":1732413602980,\"persisted\":1732413602980},\"62ac54e8-e424-41b3-889c-83f36c13676d\":{\"name\":\"Child Layout 2\",\"type\":\"layout\",\"identifier\":{\"key\":\"62ac54e8-e424-41b3-889c-83f36c13676d\",\"namespace\":\"\"},\"composition\":[],\"configuration\":{\"items\":[],\"layoutGrid\":[10,10]},\"notes\":\"framework/generateLocalStorageData.e2e.spec.js\\nGenerate Visual Test Data @localStorage @generatedata\\nGenerate display layout with 2 child display layouts\\nchrome\",\"modified\":1732413604120,\"location\":\"80d08d10-2f2b-4ebb-847c-2385016718e7\",\"created\":1732413604120,\"persisted\":1732413604120}}" - }, - { - "name": "mct-recent-objects", - "value": "[{\"objectPath\":[{\"identifier\":{\"key\":\"80d08d10-2f2b-4ebb-847c-2385016718e7\",\"namespace\":\"\"},\"name\":\"Parent Display Layout\",\"type\":\"layout\",\"composition\":[{\"key\":\"cdff8398-81e5-4a37-8c2d-8ec0a0ed7d16\",\"namespace\":\"\"},{\"key\":\"62ac54e8-e424-41b3-889c-83f36c13676d\",\"namespace\":\"\"}],\"configuration\":{\"items\":[{\"width\":32,\"height\":18,\"x\":1,\"y\":1,\"identifier\":{\"key\":\"cdff8398-81e5-4a37-8c2d-8ec0a0ed7d16\",\"namespace\":\"\"},\"hasFrame\":true,\"fontSize\":\"default\",\"font\":\"default\",\"type\":\"subobject-view\",\"id\":\"a71236dd-9178-4b96-88d9-b93ceee12f32\"}],\"layoutGrid\":[10,10]},\"notes\":\"framework/generateLocalStorageData.e2e.spec.js\\nGenerate Visual Test Data @localStorage @generatedata\\nGenerate display layout with 2 child display layouts\\nchrome\",\"modified\":1732413604120,\"location\":\"mine\",\"created\":1732413601880,\"persisted\":1732413604120},{\"identifier\":{\"key\":\"mine\",\"namespace\":\"\"},\"name\":\"My Items\",\"type\":\"folder\",\"composition\":[{\"key\":\"80d08d10-2f2b-4ebb-847c-2385016718e7\",\"namespace\":\"\"}],\"location\":\"ROOT\",\"modified\":1732413601880,\"created\":1732413600900,\"persisted\":1732413601880},{\"identifier\":{\"key\":\"ROOT\",\"namespace\":\"\"},\"name\":\"Open MCT\",\"type\":\"root\",\"composition\":[{\"key\":\"mine\",\"namespace\":\"\"}]}],\"navigationPath\":\"/browse/mine/80d08d10-2f2b-4ebb-847c-2385016718e7\",\"domainObject\":{\"identifier\":{\"key\":\"80d08d10-2f2b-4ebb-847c-2385016718e7\",\"namespace\":\"\"},\"name\":\"Parent Display Layout\",\"type\":\"layout\",\"composition\":[{\"key\":\"cdff8398-81e5-4a37-8c2d-8ec0a0ed7d16\",\"namespace\":\"\"},{\"key\":\"62ac54e8-e424-41b3-889c-83f36c13676d\",\"namespace\":\"\"}],\"configuration\":{\"items\":[{\"width\":32,\"height\":18,\"x\":1,\"y\":1,\"identifier\":{\"key\":\"cdff8398-81e5-4a37-8c2d-8ec0a0ed7d16\",\"namespace\":\"\"},\"hasFrame\":true,\"fontSize\":\"default\",\"font\":\"default\",\"type\":\"subobject-view\",\"id\":\"a71236dd-9178-4b96-88d9-b93ceee12f32\"}],\"layoutGrid\":[10,10]},\"notes\":\"framework/generateLocalStorageData.e2e.spec.js\\nGenerate Visual Test Data @localStorage @generatedata\\nGenerate display layout with 2 child display layouts\\nchrome\",\"modified\":1732413604120,\"location\":\"mine\",\"created\":1732413601880,\"persisted\":1732413604120}},{\"objectPath\":[{\"identifier\":{\"key\":\"62ac54e8-e424-41b3-889c-83f36c13676d\",\"namespace\":\"\"},\"name\":\"Child Layout 2\",\"type\":\"layout\",\"composition\":[],\"configuration\":{\"items\":[],\"layoutGrid\":[10,10]},\"notes\":\"framework/generateLocalStorageData.e2e.spec.js\\nGenerate Visual Test Data @localStorage @generatedata\\nGenerate display layout with 2 child display layouts\\nchrome\",\"modified\":1732413604120,\"location\":\"80d08d10-2f2b-4ebb-847c-2385016718e7\",\"created\":1732413604120,\"persisted\":1732413604120},{\"identifier\":{\"key\":\"80d08d10-2f2b-4ebb-847c-2385016718e7\",\"namespace\":\"\"},\"name\":\"Parent Display Layout\",\"type\":\"layout\",\"composition\":[{\"key\":\"cdff8398-81e5-4a37-8c2d-8ec0a0ed7d16\",\"namespace\":\"\"},{\"key\":\"62ac54e8-e424-41b3-889c-83f36c13676d\",\"namespace\":\"\"}],\"configuration\":{\"items\":[{\"width\":32,\"height\":18,\"x\":1,\"y\":1,\"identifier\":{\"key\":\"cdff8398-81e5-4a37-8c2d-8ec0a0ed7d16\",\"namespace\":\"\"},\"hasFrame\":true,\"fontSize\":\"default\",\"font\":\"default\",\"type\":\"subobject-view\",\"id\":\"a71236dd-9178-4b96-88d9-b93ceee12f32\"}],\"layoutGrid\":[10,10]},\"notes\":\"framework/generateLocalStorageData.e2e.spec.js\\nGenerate Visual Test Data @localStorage @generatedata\\nGenerate display layout with 2 child display layouts\\nchrome\",\"modified\":1732413604120,\"location\":\"mine\",\"created\":1732413601880,\"persisted\":1732413604120},{\"identifier\":{\"key\":\"mine\",\"namespace\":\"\"},\"name\":\"My Items\",\"type\":\"folder\",\"composition\":[{\"key\":\"80d08d10-2f2b-4ebb-847c-2385016718e7\",\"namespace\":\"\"}],\"location\":\"ROOT\",\"modified\":1732413601880,\"created\":1732413600900,\"persisted\":1732413601880},{\"identifier\":{\"key\":\"ROOT\",\"namespace\":\"\"},\"name\":\"Open MCT\",\"type\":\"root\",\"composition\":[{\"key\":\"mine\",\"namespace\":\"\"}]}],\"navigationPath\":\"/browse/mine/80d08d10-2f2b-4ebb-847c-2385016718e7/62ac54e8-e424-41b3-889c-83f36c13676d\",\"domainObject\":{\"identifier\":{\"key\":\"62ac54e8-e424-41b3-889c-83f36c13676d\",\"namespace\":\"\"},\"name\":\"Child Layout 2\",\"type\":\"layout\",\"composition\":[],\"configuration\":{\"items\":[],\"layoutGrid\":[10,10]},\"notes\":\"framework/generateLocalStorageData.e2e.spec.js\\nGenerate Visual Test Data @localStorage @generatedata\\nGenerate display layout with 2 child display layouts\\nchrome\",\"modified\":1732413604120,\"location\":\"80d08d10-2f2b-4ebb-847c-2385016718e7\",\"created\":1732413604120,\"persisted\":1732413604120}},{\"objectPath\":[{\"identifier\":{\"key\":\"cdff8398-81e5-4a37-8c2d-8ec0a0ed7d16\",\"namespace\":\"\"},\"name\":\"Child Layout 1\",\"type\":\"layout\",\"composition\":[],\"configuration\":{\"items\":[],\"layoutGrid\":[10,10]},\"notes\":\"framework/generateLocalStorageData.e2e.spec.js\\nGenerate Visual Test Data @localStorage @generatedata\\nGenerate display layout with 2 child display layouts\\nchrome\",\"modified\":1732413602980,\"location\":\"80d08d10-2f2b-4ebb-847c-2385016718e7\",\"created\":1732413602980,\"persisted\":1732413602980},{\"identifier\":{\"key\":\"80d08d10-2f2b-4ebb-847c-2385016718e7\",\"namespace\":\"\"},\"name\":\"Parent Display Layout\",\"type\":\"layout\",\"composition\":[{\"key\":\"cdff8398-81e5-4a37-8c2d-8ec0a0ed7d16\",\"namespace\":\"\"},{\"key\":\"62ac54e8-e424-41b3-889c-83f36c13676d\",\"namespace\":\"\"}],\"configuration\":{\"items\":[{\"width\":32,\"height\":18,\"x\":1,\"y\":1,\"identifier\":{\"key\":\"cdff8398-81e5-4a37-8c2d-8ec0a0ed7d16\",\"namespace\":\"\"},\"hasFrame\":true,\"fontSize\":\"default\",\"font\":\"default\",\"type\":\"subobject-view\",\"id\":\"a71236dd-9178-4b96-88d9-b93ceee12f32\"}],\"layoutGrid\":[10,10]},\"notes\":\"framework/generateLocalStorageData.e2e.spec.js\\nGenerate Visual Test Data @localStorage @generatedata\\nGenerate display layout with 2 child display layouts\\nchrome\",\"modified\":1732413604120,\"location\":\"mine\",\"created\":1732413601880,\"persisted\":1732413604120},{\"identifier\":{\"key\":\"mine\",\"namespace\":\"\"},\"name\":\"My Items\",\"type\":\"folder\",\"composition\":[{\"key\":\"80d08d10-2f2b-4ebb-847c-2385016718e7\",\"namespace\":\"\"}],\"location\":\"ROOT\",\"modified\":1732413601880,\"created\":1732413600900,\"persisted\":1732413601880},{\"identifier\":{\"key\":\"ROOT\",\"namespace\":\"\"},\"name\":\"Open MCT\",\"type\":\"root\",\"composition\":[{\"key\":\"mine\",\"namespace\":\"\"}]}],\"navigationPath\":\"/browse/mine/80d08d10-2f2b-4ebb-847c-2385016718e7/cdff8398-81e5-4a37-8c2d-8ec0a0ed7d16\",\"domainObject\":{\"identifier\":{\"key\":\"cdff8398-81e5-4a37-8c2d-8ec0a0ed7d16\",\"namespace\":\"\"},\"name\":\"Child Layout 1\",\"type\":\"layout\",\"composition\":[],\"configuration\":{\"items\":[],\"layoutGrid\":[10,10]},\"notes\":\"framework/generateLocalStorageData.e2e.spec.js\\nGenerate Visual Test Data @localStorage @generatedata\\nGenerate display layout with 2 child display layouts\\nchrome\",\"modified\":1732413602980,\"location\":\"80d08d10-2f2b-4ebb-847c-2385016718e7\",\"created\":1732413602980,\"persisted\":1732413602980}},{\"objectPath\":[{\"identifier\":{\"key\":\"mine\",\"namespace\":\"\"},\"name\":\"My Items\",\"type\":\"folder\",\"composition\":[{\"key\":\"80d08d10-2f2b-4ebb-847c-2385016718e7\",\"namespace\":\"\"}],\"location\":\"ROOT\",\"modified\":1732413601880,\"created\":1732413600900,\"persisted\":1732413601880},{\"identifier\":{\"key\":\"ROOT\",\"namespace\":\"\"},\"name\":\"Open MCT\",\"type\":\"root\",\"composition\":[{\"key\":\"mine\",\"namespace\":\"\"}]}],\"navigationPath\":\"/browse/mine\",\"domainObject\":{\"identifier\":{\"key\":\"mine\",\"namespace\":\"\"},\"name\":\"My Items\",\"type\":\"folder\",\"composition\":[{\"key\":\"80d08d10-2f2b-4ebb-847c-2385016718e7\",\"namespace\":\"\"}],\"location\":\"ROOT\",\"modified\":1732413601880,\"created\":1732413600900,\"persisted\":1732413601880}}]" + "value": "{\"mine\":{\"identifier\":{\"key\":\"mine\",\"namespace\":\"\"},\"name\":\"My Items\",\"type\":\"folder\",\"composition\":[{\"key\":\"f11f93ab-8918-4732-b20c-617b7b2e16ad\",\"namespace\":\"\"}],\"location\":\"ROOT\",\"modified\":1732413604666,\"created\":1732413603039,\"persisted\":1732413604666},\"f11f93ab-8918-4732-b20c-617b7b2e16ad\":{\"identifier\":{\"key\":\"f11f93ab-8918-4732-b20c-617b7b2e16ad\",\"namespace\":\"\"},\"name\":\"Parent Display Layout\",\"type\":\"layout\",\"composition\":[{\"key\":\"034e9aab-8b24-493b-876f-80ed474b61fb\",\"namespace\":\"\"},{\"key\":\"ea2f53e0-3376-4ba7-8df7-349339e41d64\",\"namespace\":\"\"}],\"configuration\":{\"items\":[{\"width\":32,\"height\":18,\"x\":1,\"y\":30,\"identifier\":{\"key\":\"034e9aab-8b24-493b-876f-80ed474b61fb\",\"namespace\":\"\"},\"hasFrame\":true,\"fontSize\":\"default\",\"font\":\"default\",\"type\":\"subobject-view\",\"id\":\"94b55aa2-8f61-431a-9453-4bbfda9119fb\"},{\"width\":32,\"height\":18,\"x\":30,\"y\":1,\"identifier\":{\"key\":\"ea2f53e0-3376-4ba7-8df7-349339e41d64\",\"namespace\":\"\"},\"hasFrame\":true,\"fontSize\":\"default\",\"font\":\"default\",\"type\":\"subobject-view\",\"id\":\"97d028f7-ffa6-494b-96d6-d8526e399766\"}],\"layoutGrid\":[10,10]},\"notes\":\"framework/generateLocalStorageData.e2e.spec.js\\nGenerate Visual Test Data @localStorage @generatedata @clock\\nGenerate display layout with 2 child display layouts\\nchrome\",\"modified\":1732413607743,\"location\":\"mine\",\"created\":1732413604666,\"persisted\":1732413607743},\"034e9aab-8b24-493b-876f-80ed474b61fb\":{\"name\":\"Child Layout 1\",\"type\":\"layout\",\"identifier\":{\"key\":\"034e9aab-8b24-493b-876f-80ed474b61fb\",\"namespace\":\"\"},\"composition\":[],\"configuration\":{\"items\":[],\"layoutGrid\":[10,10]},\"notes\":\"framework/generateLocalStorageData.e2e.spec.js\\nGenerate Visual Test Data @localStorage @generatedata @clock\\nGenerate display layout with 2 child display layouts\\nchrome\",\"modified\":1732413605564,\"location\":\"f11f93ab-8918-4732-b20c-617b7b2e16ad\",\"created\":1732413605564,\"persisted\":1732413605564},\"ea2f53e0-3376-4ba7-8df7-349339e41d64\":{\"name\":\"Child Layout 2\",\"type\":\"layout\",\"identifier\":{\"key\":\"ea2f53e0-3376-4ba7-8df7-349339e41d64\",\"namespace\":\"\"},\"composition\":[],\"configuration\":{\"items\":[],\"layoutGrid\":[10,10]},\"notes\":\"framework/generateLocalStorageData.e2e.spec.js\\nGenerate Visual Test Data @localStorage @generatedata @clock\\nGenerate display layout with 2 child display layouts\\nchrome\",\"modified\":1732413606944,\"location\":\"f11f93ab-8918-4732-b20c-617b7b2e16ad\",\"created\":1732413606944,\"persisted\":1732413606944}}" }, { "name": "mct-tree-expanded", "value": "[]" + }, + { + "name": "mct-recent-objects", + "value": "[{\"objectPath\":[{\"identifier\":{\"key\":\"f11f93ab-8918-4732-b20c-617b7b2e16ad\",\"namespace\":\"\"},\"name\":\"Parent Display Layout\",\"type\":\"layout\",\"composition\":[{\"key\":\"034e9aab-8b24-493b-876f-80ed474b61fb\",\"namespace\":\"\"},{\"key\":\"ea2f53e0-3376-4ba7-8df7-349339e41d64\",\"namespace\":\"\"}],\"configuration\":{\"items\":[{\"width\":32,\"height\":18,\"x\":1,\"y\":1,\"identifier\":{\"key\":\"034e9aab-8b24-493b-876f-80ed474b61fb\",\"namespace\":\"\"},\"hasFrame\":true,\"fontSize\":\"default\",\"font\":\"default\",\"type\":\"subobject-view\",\"id\":\"94b55aa2-8f61-431a-9453-4bbfda9119fb\"}],\"layoutGrid\":[10,10]},\"notes\":\"framework/generateLocalStorageData.e2e.spec.js\\nGenerate Visual Test Data @localStorage @generatedata @clock\\nGenerate display layout with 2 child display layouts\\nchrome\",\"modified\":1732413606944,\"location\":\"mine\",\"created\":1732413604666,\"persisted\":1732413606944},{\"identifier\":{\"key\":\"mine\",\"namespace\":\"\"},\"name\":\"My Items\",\"type\":\"folder\",\"composition\":[{\"key\":\"f11f93ab-8918-4732-b20c-617b7b2e16ad\",\"namespace\":\"\"}],\"location\":\"ROOT\",\"modified\":1732413604666,\"created\":1732413603039,\"persisted\":1732413604666},{\"identifier\":{\"key\":\"ROOT\",\"namespace\":\"\"},\"name\":\"Open MCT\",\"type\":\"root\",\"composition\":[{\"key\":\"mine\",\"namespace\":\"\"}]}],\"navigationPath\":\"/browse/mine/f11f93ab-8918-4732-b20c-617b7b2e16ad\",\"domainObject\":{\"identifier\":{\"key\":\"f11f93ab-8918-4732-b20c-617b7b2e16ad\",\"namespace\":\"\"},\"name\":\"Parent Display Layout\",\"type\":\"layout\",\"composition\":[{\"key\":\"034e9aab-8b24-493b-876f-80ed474b61fb\",\"namespace\":\"\"},{\"key\":\"ea2f53e0-3376-4ba7-8df7-349339e41d64\",\"namespace\":\"\"}],\"configuration\":{\"items\":[{\"width\":32,\"height\":18,\"x\":1,\"y\":1,\"identifier\":{\"key\":\"034e9aab-8b24-493b-876f-80ed474b61fb\",\"namespace\":\"\"},\"hasFrame\":true,\"fontSize\":\"default\",\"font\":\"default\",\"type\":\"subobject-view\",\"id\":\"94b55aa2-8f61-431a-9453-4bbfda9119fb\"}],\"layoutGrid\":[10,10]},\"notes\":\"framework/generateLocalStorageData.e2e.spec.js\\nGenerate Visual Test Data @localStorage @generatedata @clock\\nGenerate display layout with 2 child display layouts\\nchrome\",\"modified\":1732413606944,\"location\":\"mine\",\"created\":1732413604666,\"persisted\":1732413606944}},{\"objectPath\":[{\"identifier\":{\"key\":\"ea2f53e0-3376-4ba7-8df7-349339e41d64\",\"namespace\":\"\"},\"name\":\"Child Layout 2\",\"type\":\"layout\",\"composition\":[],\"configuration\":{\"items\":[],\"layoutGrid\":[10,10]},\"notes\":\"framework/generateLocalStorageData.e2e.spec.js\\nGenerate Visual Test Data @localStorage @generatedata @clock\\nGenerate display layout with 2 child display layouts\\nchrome\",\"modified\":1732413606944,\"location\":\"f11f93ab-8918-4732-b20c-617b7b2e16ad\",\"created\":1732413606944,\"persisted\":1732413606944},{\"identifier\":{\"key\":\"f11f93ab-8918-4732-b20c-617b7b2e16ad\",\"namespace\":\"\"},\"name\":\"Parent Display Layout\",\"type\":\"layout\",\"composition\":[{\"key\":\"034e9aab-8b24-493b-876f-80ed474b61fb\",\"namespace\":\"\"},{\"key\":\"ea2f53e0-3376-4ba7-8df7-349339e41d64\",\"namespace\":\"\"}],\"configuration\":{\"items\":[{\"width\":32,\"height\":18,\"x\":1,\"y\":1,\"identifier\":{\"key\":\"034e9aab-8b24-493b-876f-80ed474b61fb\",\"namespace\":\"\"},\"hasFrame\":true,\"fontSize\":\"default\",\"font\":\"default\",\"type\":\"subobject-view\",\"id\":\"94b55aa2-8f61-431a-9453-4bbfda9119fb\"}],\"layoutGrid\":[10,10]},\"notes\":\"framework/generateLocalStorageData.e2e.spec.js\\nGenerate Visual Test Data @localStorage @generatedata @clock\\nGenerate display layout with 2 child display layouts\\nchrome\",\"modified\":1732413606944,\"location\":\"mine\",\"created\":1732413604666,\"persisted\":1732413606944},{\"identifier\":{\"key\":\"mine\",\"namespace\":\"\"},\"name\":\"My Items\",\"type\":\"folder\",\"composition\":[{\"key\":\"f11f93ab-8918-4732-b20c-617b7b2e16ad\",\"namespace\":\"\"}],\"location\":\"ROOT\",\"modified\":1732413604666,\"created\":1732413603039,\"persisted\":1732413604666},{\"identifier\":{\"key\":\"ROOT\",\"namespace\":\"\"},\"name\":\"Open MCT\",\"type\":\"root\",\"composition\":[{\"key\":\"mine\",\"namespace\":\"\"}]}],\"navigationPath\":\"/browse/mine/f11f93ab-8918-4732-b20c-617b7b2e16ad/ea2f53e0-3376-4ba7-8df7-349339e41d64\",\"domainObject\":{\"identifier\":{\"key\":\"ea2f53e0-3376-4ba7-8df7-349339e41d64\",\"namespace\":\"\"},\"name\":\"Child Layout 2\",\"type\":\"layout\",\"composition\":[],\"configuration\":{\"items\":[],\"layoutGrid\":[10,10]},\"notes\":\"framework/generateLocalStorageData.e2e.spec.js\\nGenerate Visual Test Data @localStorage @generatedata @clock\\nGenerate display layout with 2 child display layouts\\nchrome\",\"modified\":1732413606944,\"location\":\"f11f93ab-8918-4732-b20c-617b7b2e16ad\",\"created\":1732413606944,\"persisted\":1732413606944}},{\"objectPath\":[{\"identifier\":{\"key\":\"034e9aab-8b24-493b-876f-80ed474b61fb\",\"namespace\":\"\"},\"name\":\"Child Layout 1\",\"type\":\"layout\",\"composition\":[],\"configuration\":{\"items\":[],\"layoutGrid\":[10,10]},\"notes\":\"framework/generateLocalStorageData.e2e.spec.js\\nGenerate Visual Test Data @localStorage @generatedata @clock\\nGenerate display layout with 2 child display layouts\\nchrome\",\"modified\":1732413605564,\"location\":\"f11f93ab-8918-4732-b20c-617b7b2e16ad\",\"created\":1732413605564,\"persisted\":1732413605564},{\"identifier\":{\"key\":\"f11f93ab-8918-4732-b20c-617b7b2e16ad\",\"namespace\":\"\"},\"name\":\"Parent Display Layout\",\"type\":\"layout\",\"composition\":[{\"key\":\"034e9aab-8b24-493b-876f-80ed474b61fb\",\"namespace\":\"\"},{\"key\":\"ea2f53e0-3376-4ba7-8df7-349339e41d64\",\"namespace\":\"\"}],\"configuration\":{\"items\":[{\"width\":32,\"height\":18,\"x\":1,\"y\":1,\"identifier\":{\"key\":\"034e9aab-8b24-493b-876f-80ed474b61fb\",\"namespace\":\"\"},\"hasFrame\":true,\"fontSize\":\"default\",\"font\":\"default\",\"type\":\"subobject-view\",\"id\":\"94b55aa2-8f61-431a-9453-4bbfda9119fb\"}],\"layoutGrid\":[10,10]},\"notes\":\"framework/generateLocalStorageData.e2e.spec.js\\nGenerate Visual Test Data @localStorage @generatedata @clock\\nGenerate display layout with 2 child display layouts\\nchrome\",\"modified\":1732413606944,\"location\":\"mine\",\"created\":1732413604666,\"persisted\":1732413606944},{\"identifier\":{\"key\":\"mine\",\"namespace\":\"\"},\"name\":\"My Items\",\"type\":\"folder\",\"composition\":[{\"key\":\"f11f93ab-8918-4732-b20c-617b7b2e16ad\",\"namespace\":\"\"}],\"location\":\"ROOT\",\"modified\":1732413604666,\"created\":1732413603039,\"persisted\":1732413604666},{\"identifier\":{\"key\":\"ROOT\",\"namespace\":\"\"},\"name\":\"Open MCT\",\"type\":\"root\",\"composition\":[{\"key\":\"mine\",\"namespace\":\"\"}]}],\"navigationPath\":\"/browse/mine/f11f93ab-8918-4732-b20c-617b7b2e16ad/034e9aab-8b24-493b-876f-80ed474b61fb\",\"domainObject\":{\"identifier\":{\"key\":\"034e9aab-8b24-493b-876f-80ed474b61fb\",\"namespace\":\"\"},\"name\":\"Child Layout 1\",\"type\":\"layout\",\"composition\":[],\"configuration\":{\"items\":[],\"layoutGrid\":[10,10]},\"notes\":\"framework/generateLocalStorageData.e2e.spec.js\\nGenerate Visual Test Data @localStorage @generatedata @clock\\nGenerate display layout with 2 child display layouts\\nchrome\",\"modified\":1732413605564,\"location\":\"f11f93ab-8918-4732-b20c-617b7b2e16ad\",\"created\":1732413605564,\"persisted\":1732413605564}},{\"objectPath\":[{\"identifier\":{\"key\":\"mine\",\"namespace\":\"\"},\"name\":\"My Items\",\"type\":\"folder\",\"composition\":[{\"key\":\"f11f93ab-8918-4732-b20c-617b7b2e16ad\",\"namespace\":\"\"}],\"location\":\"ROOT\",\"modified\":1732413604666,\"created\":1732413603039,\"persisted\":1732413604666},{\"identifier\":{\"key\":\"ROOT\",\"namespace\":\"\"},\"name\":\"Open MCT\",\"type\":\"root\",\"composition\":[{\"key\":\"mine\",\"namespace\":\"\"}]}],\"navigationPath\":\"/browse/mine\",\"domainObject\":{\"identifier\":{\"key\":\"mine\",\"namespace\":\"\"},\"name\":\"My Items\",\"type\":\"folder\",\"composition\":[{\"key\":\"f11f93ab-8918-4732-b20c-617b7b2e16ad\",\"namespace\":\"\"}],\"location\":\"ROOT\",\"modified\":1732413604666,\"created\":1732413603039,\"persisted\":1732413604666}}]" } ] } diff --git a/e2e/test-data/display_layout_with_child_overlay_plot.json b/e2e/test-data/display_layout_with_child_overlay_plot.json index b6854feba2..5bebeeeb84 100644 --- a/e2e/test-data/display_layout_with_child_overlay_plot.json +++ b/e2e/test-data/display_layout_with_child_overlay_plot.json @@ -6,7 +6,7 @@ "localStorage": [ { "name": "mct", - "value": "{\"mine\":{\"identifier\":{\"key\":\"mine\",\"namespace\":\"\"},\"name\":\"My Items\",\"type\":\"folder\",\"composition\":[{\"key\":\"29836e66-111a-45f8-81ed-f662661be9f9\",\"namespace\":\"\"}],\"location\":\"ROOT\",\"modified\":1732413602140,\"created\":1732413600860,\"persisted\":1732413602140},\"29836e66-111a-45f8-81ed-f662661be9f9\":{\"identifier\":{\"key\":\"29836e66-111a-45f8-81ed-f662661be9f9\",\"namespace\":\"\"},\"name\":\"Parent Display Layout\",\"type\":\"layout\",\"composition\":[{\"key\":\"55cd0300-6e57-4992-b670-0c2880c0e6b2\",\"namespace\":\"\"}],\"configuration\":{\"items\":[{\"width\":32,\"height\":18,\"x\":1,\"y\":1,\"identifier\":{\"key\":\"55cd0300-6e57-4992-b670-0c2880c0e6b2\",\"namespace\":\"\"},\"hasFrame\":true,\"fontSize\":\"default\",\"font\":\"default\",\"type\":\"subobject-view\",\"id\":\"022720f7-a6b5-40c3-b051-75f5d10a9042\"}],\"layoutGrid\":[10,10]},\"notes\":\"framework/generateLocalStorageData.e2e.spec.js\\nGenerate Visual Test Data @localStorage @generatedata\\nGenerate display layout with 1 child overlay plot\\nchrome\",\"modified\":1732413604780,\"location\":\"mine\",\"created\":1732413602140,\"persisted\":1732413604780},\"55cd0300-6e57-4992-b670-0c2880c0e6b2\":{\"identifier\":{\"key\":\"55cd0300-6e57-4992-b670-0c2880c0e6b2\",\"namespace\":\"\"},\"name\":\"Child Overlay Plot 1\",\"type\":\"telemetry.plot.overlay\",\"composition\":[{\"key\":\"ec13f652-4636-4763-8e88-898144cbc6f2\",\"namespace\":\"\"}],\"configuration\":{\"series\":[{\"identifier\":{\"key\":\"ec13f652-4636-4763-8e88-898144cbc6f2\",\"namespace\":\"\"}}],\"useIndependentTime\":true,\"timeOptions\":{\"clockOffsets\":{\"start\":-1800000,\"end\":30000},\"fixedOffsets\":{\"start\":1731438671000,\"end\":1731442271000},\"clock\":\"local\",\"mode\":\"fixed\"}},\"notes\":\"framework/generateLocalStorageData.e2e.spec.js\\nGenerate Visual Test Data @localStorage @generatedata\\nGenerate display layout with 1 child overlay plot\\nchrome\",\"modified\":1732413605500,\"location\":\"29836e66-111a-45f8-81ed-f662661be9f9\",\"created\":1732413603280,\"persisted\":1732413605500},\"ec13f652-4636-4763-8e88-898144cbc6f2\":{\"name\":\"Child SWG 1\",\"type\":\"generator\",\"identifier\":{\"key\":\"ec13f652-4636-4763-8e88-898144cbc6f2\",\"namespace\":\"\"},\"telemetry\":{\"period\":10,\"amplitude\":1,\"offset\":0,\"dataRateInHz\":1,\"phase\":0,\"randomness\":0,\"loadDelay\":0,\"infinityValues\":false,\"exceedFloat32\":false,\"staleness\":false},\"notes\":\"framework/generateLocalStorageData.e2e.spec.js\\nGenerate Visual Test Data @localStorage @generatedata\\nGenerate display layout with 1 child overlay plot\\nchrome\",\"modified\":1732413604440,\"location\":\"55cd0300-6e57-4992-b670-0c2880c0e6b2\",\"created\":1732413604440,\"persisted\":1732413604440}}" + "value": "{\"mine\":{\"identifier\":{\"key\":\"mine\",\"namespace\":\"\"},\"name\":\"My Items\",\"type\":\"folder\",\"composition\":[{\"key\":\"712d07f1-3585-465a-a6db-3c40a9edcde7\",\"namespace\":\"\"}],\"location\":\"ROOT\",\"modified\":1732413604993,\"created\":1732413603983,\"persisted\":1732413604993},\"712d07f1-3585-465a-a6db-3c40a9edcde7\":{\"identifier\":{\"key\":\"712d07f1-3585-465a-a6db-3c40a9edcde7\",\"namespace\":\"\"},\"name\":\"Parent Display Layout\",\"type\":\"layout\",\"composition\":[{\"key\":\"6731f693-b09e-46de-a0cb-9331f1fb2e6d\",\"namespace\":\"\"}],\"configuration\":{\"items\":[{\"width\":32,\"height\":18,\"x\":1,\"y\":1,\"identifier\":{\"key\":\"6731f693-b09e-46de-a0cb-9331f1fb2e6d\",\"namespace\":\"\"},\"hasFrame\":true,\"fontSize\":\"default\",\"font\":\"default\",\"type\":\"subobject-view\",\"id\":\"7aea18ca-1537-4f4f-98e4-a57b4cd8f9a7\"}],\"layoutGrid\":[10,10]},\"notes\":\"framework/generateLocalStorageData.e2e.spec.js\\nGenerate Visual Test Data @localStorage @generatedata @clock\\nGenerate display layout with 1 child overlay plot\\nchrome\",\"modified\":1732413608088.7,\"location\":\"mine\",\"created\":1732413604993,\"persisted\":1732413608088.7},\"6731f693-b09e-46de-a0cb-9331f1fb2e6d\":{\"identifier\":{\"key\":\"6731f693-b09e-46de-a0cb-9331f1fb2e6d\",\"namespace\":\"\"},\"name\":\"Child Overlay Plot 1\",\"type\":\"telemetry.plot.overlay\",\"composition\":[{\"key\":\"fa050882-fcec-49c0-aa78-236a595accaf\",\"namespace\":\"\"}],\"configuration\":{\"series\":[{\"identifier\":{\"key\":\"fa050882-fcec-49c0-aa78-236a595accaf\",\"namespace\":\"\"}}],\"useIndependentTime\":true,\"timeOptions\":{\"clockOffsets\":{\"start\":-1800000,\"end\":30000},\"fixedOffsets\":{\"start\":1731438671000,\"end\":1731442271000},\"clock\":\"local\",\"mode\":\"fixed\"}},\"notes\":\"framework/generateLocalStorageData.e2e.spec.js\\nGenerate Visual Test Data @localStorage @generatedata @clock\\nGenerate display layout with 1 child overlay plot\\nchrome\",\"modified\":1732413608703.7,\"location\":\"712d07f1-3585-465a-a6db-3c40a9edcde7\",\"created\":1732413606392.7,\"persisted\":1732413608703.7},\"fa050882-fcec-49c0-aa78-236a595accaf\":{\"name\":\"Child SWG 1\",\"type\":\"generator\",\"identifier\":{\"key\":\"fa050882-fcec-49c0-aa78-236a595accaf\",\"namespace\":\"\"},\"telemetry\":{\"period\":10,\"amplitude\":1,\"offset\":0,\"dataRateInHz\":1,\"phase\":0,\"randomness\":0,\"loadDelay\":0,\"infinityValues\":false,\"exceedFloat32\":false,\"staleness\":false},\"notes\":\"framework/generateLocalStorageData.e2e.spec.js\\nGenerate Visual Test Data @localStorage @generatedata @clock\\nGenerate display layout with 1 child overlay plot\\nchrome\",\"modified\":1732413607788,\"location\":\"6731f693-b09e-46de-a0cb-9331f1fb2e6d\",\"created\":1732413607788,\"persisted\":1732413607788}}" }, { "name": "mct-tree-expanded", @@ -18,7 +18,7 @@ }, { "name": "mct-recent-objects", - "value": "[{\"objectPath\":[{\"identifier\":{\"key\":\"29836e66-111a-45f8-81ed-f662661be9f9\",\"namespace\":\"\"},\"name\":\"Parent Display Layout\",\"type\":\"layout\",\"composition\":[{\"key\":\"55cd0300-6e57-4992-b670-0c2880c0e6b2\",\"namespace\":\"\"}],\"configuration\":{\"items\":[],\"layoutGrid\":[10,10]},\"notes\":\"framework/generateLocalStorageData.e2e.spec.js\\nGenerate Visual Test Data @localStorage @generatedata\\nGenerate display layout with 1 child overlay plot\\nchrome\",\"modified\":1732413603280,\"location\":\"mine\",\"created\":1732413602140,\"persisted\":1732413603280},{\"identifier\":{\"key\":\"mine\",\"namespace\":\"\"},\"name\":\"My Items\",\"type\":\"folder\",\"composition\":[{\"key\":\"29836e66-111a-45f8-81ed-f662661be9f9\",\"namespace\":\"\"}],\"location\":\"ROOT\",\"modified\":1732413602140,\"created\":1732413600860,\"persisted\":1732413602140},{\"identifier\":{\"key\":\"ROOT\",\"namespace\":\"\"},\"name\":\"Open MCT\",\"type\":\"root\",\"composition\":[{\"key\":\"mine\",\"namespace\":\"\"}]}],\"navigationPath\":\"/browse/mine/29836e66-111a-45f8-81ed-f662661be9f9\",\"domainObject\":{\"identifier\":{\"key\":\"29836e66-111a-45f8-81ed-f662661be9f9\",\"namespace\":\"\"},\"name\":\"Parent Display Layout\",\"type\":\"layout\",\"composition\":[{\"key\":\"55cd0300-6e57-4992-b670-0c2880c0e6b2\",\"namespace\":\"\"}],\"configuration\":{\"items\":[],\"layoutGrid\":[10,10]},\"notes\":\"framework/generateLocalStorageData.e2e.spec.js\\nGenerate Visual Test Data @localStorage @generatedata\\nGenerate display layout with 1 child overlay plot\\nchrome\",\"modified\":1732413603280,\"location\":\"mine\",\"created\":1732413602140,\"persisted\":1732413603280}},{\"objectPath\":[{\"identifier\":{\"key\":\"ec13f652-4636-4763-8e88-898144cbc6f2\",\"namespace\":\"\"},\"name\":\"Child SWG 1\",\"type\":\"generator\",\"telemetry\":{\"period\":10,\"amplitude\":1,\"offset\":0,\"dataRateInHz\":1,\"phase\":0,\"randomness\":0,\"loadDelay\":0,\"infinityValues\":false,\"exceedFloat32\":false,\"staleness\":false},\"notes\":\"framework/generateLocalStorageData.e2e.spec.js\\nGenerate Visual Test Data @localStorage @generatedata\\nGenerate display layout with 1 child overlay plot\\nchrome\",\"modified\":1732413604440,\"location\":\"55cd0300-6e57-4992-b670-0c2880c0e6b2\",\"created\":1732413604440,\"persisted\":1732413604440},{\"identifier\":{\"key\":\"55cd0300-6e57-4992-b670-0c2880c0e6b2\",\"namespace\":\"\"},\"name\":\"Child Overlay Plot 1\",\"type\":\"telemetry.plot.overlay\",\"composition\":[{\"key\":\"ec13f652-4636-4763-8e88-898144cbc6f2\",\"namespace\":\"\"}],\"configuration\":{\"series\":[]},\"notes\":\"framework/generateLocalStorageData.e2e.spec.js\\nGenerate Visual Test Data @localStorage @generatedata\\nGenerate display layout with 1 child overlay plot\\nchrome\",\"modified\":1732413604440,\"location\":\"29836e66-111a-45f8-81ed-f662661be9f9\",\"created\":1732413603280,\"persisted\":1732413604440},{\"identifier\":{\"key\":\"29836e66-111a-45f8-81ed-f662661be9f9\",\"namespace\":\"\"},\"name\":\"Parent Display Layout\",\"type\":\"layout\",\"composition\":[{\"key\":\"55cd0300-6e57-4992-b670-0c2880c0e6b2\",\"namespace\":\"\"}],\"configuration\":{\"items\":[],\"layoutGrid\":[10,10]},\"notes\":\"framework/generateLocalStorageData.e2e.spec.js\\nGenerate Visual Test Data @localStorage @generatedata\\nGenerate display layout with 1 child overlay plot\\nchrome\",\"modified\":1732413603280,\"location\":\"mine\",\"created\":1732413602140,\"persisted\":1732413603280},{\"identifier\":{\"key\":\"mine\",\"namespace\":\"\"},\"name\":\"My Items\",\"type\":\"folder\",\"composition\":[{\"key\":\"29836e66-111a-45f8-81ed-f662661be9f9\",\"namespace\":\"\"}],\"location\":\"ROOT\",\"modified\":1732413602140,\"created\":1732413600860,\"persisted\":1732413602140},{\"identifier\":{\"key\":\"ROOT\",\"namespace\":\"\"},\"name\":\"Open MCT\",\"type\":\"root\",\"composition\":[{\"key\":\"mine\",\"namespace\":\"\"}]}],\"navigationPath\":\"/browse/mine/29836e66-111a-45f8-81ed-f662661be9f9/55cd0300-6e57-4992-b670-0c2880c0e6b2/ec13f652-4636-4763-8e88-898144cbc6f2\",\"domainObject\":{\"identifier\":{\"key\":\"ec13f652-4636-4763-8e88-898144cbc6f2\",\"namespace\":\"\"},\"name\":\"Child SWG 1\",\"type\":\"generator\",\"telemetry\":{\"period\":10,\"amplitude\":1,\"offset\":0,\"dataRateInHz\":1,\"phase\":0,\"randomness\":0,\"loadDelay\":0,\"infinityValues\":false,\"exceedFloat32\":false,\"staleness\":false},\"notes\":\"framework/generateLocalStorageData.e2e.spec.js\\nGenerate Visual Test Data @localStorage @generatedata\\nGenerate display layout with 1 child overlay plot\\nchrome\",\"modified\":1732413604440,\"location\":\"55cd0300-6e57-4992-b670-0c2880c0e6b2\",\"created\":1732413604440,\"persisted\":1732413604440}},{\"objectPath\":[{\"identifier\":{\"key\":\"55cd0300-6e57-4992-b670-0c2880c0e6b2\",\"namespace\":\"\"},\"name\":\"Child Overlay Plot 1\",\"type\":\"telemetry.plot.overlay\",\"composition\":[{\"key\":\"ec13f652-4636-4763-8e88-898144cbc6f2\",\"namespace\":\"\"}],\"configuration\":{\"series\":[]},\"notes\":\"framework/generateLocalStorageData.e2e.spec.js\\nGenerate Visual Test Data @localStorage @generatedata\\nGenerate display layout with 1 child overlay plot\\nchrome\",\"modified\":1732413604440,\"location\":\"29836e66-111a-45f8-81ed-f662661be9f9\",\"created\":1732413603280,\"persisted\":1732413604440},{\"identifier\":{\"key\":\"29836e66-111a-45f8-81ed-f662661be9f9\",\"namespace\":\"\"},\"name\":\"Parent Display Layout\",\"type\":\"layout\",\"composition\":[{\"key\":\"55cd0300-6e57-4992-b670-0c2880c0e6b2\",\"namespace\":\"\"}],\"configuration\":{\"items\":[],\"layoutGrid\":[10,10]},\"notes\":\"framework/generateLocalStorageData.e2e.spec.js\\nGenerate Visual Test Data @localStorage @generatedata\\nGenerate display layout with 1 child overlay plot\\nchrome\",\"modified\":1732413603280,\"location\":\"mine\",\"created\":1732413602140,\"persisted\":1732413603280},{\"identifier\":{\"key\":\"mine\",\"namespace\":\"\"},\"name\":\"My Items\",\"type\":\"folder\",\"composition\":[{\"key\":\"29836e66-111a-45f8-81ed-f662661be9f9\",\"namespace\":\"\"}],\"location\":\"ROOT\",\"modified\":1732413602140,\"created\":1732413600860,\"persisted\":1732413602140},{\"identifier\":{\"key\":\"ROOT\",\"namespace\":\"\"},\"name\":\"Open MCT\",\"type\":\"root\",\"composition\":[{\"key\":\"mine\",\"namespace\":\"\"}]}],\"navigationPath\":\"/browse/mine/29836e66-111a-45f8-81ed-f662661be9f9/55cd0300-6e57-4992-b670-0c2880c0e6b2\",\"domainObject\":{\"identifier\":{\"key\":\"55cd0300-6e57-4992-b670-0c2880c0e6b2\",\"namespace\":\"\"},\"name\":\"Child Overlay Plot 1\",\"type\":\"telemetry.plot.overlay\",\"composition\":[{\"key\":\"ec13f652-4636-4763-8e88-898144cbc6f2\",\"namespace\":\"\"}],\"configuration\":{\"series\":[]},\"notes\":\"framework/generateLocalStorageData.e2e.spec.js\\nGenerate Visual Test Data @localStorage @generatedata\\nGenerate display layout with 1 child overlay plot\\nchrome\",\"modified\":1732413604440,\"location\":\"29836e66-111a-45f8-81ed-f662661be9f9\",\"created\":1732413603280,\"persisted\":1732413604440}},{\"objectPath\":[{\"identifier\":{\"key\":\"mine\",\"namespace\":\"\"},\"name\":\"My Items\",\"type\":\"folder\",\"composition\":[{\"key\":\"29836e66-111a-45f8-81ed-f662661be9f9\",\"namespace\":\"\"}],\"location\":\"ROOT\",\"modified\":1732413602140,\"created\":1732413600860,\"persisted\":1732413602140},{\"identifier\":{\"key\":\"ROOT\",\"namespace\":\"\"},\"name\":\"Open MCT\",\"type\":\"root\",\"composition\":[{\"key\":\"mine\",\"namespace\":\"\"}]}],\"navigationPath\":\"/browse/mine\",\"domainObject\":{\"identifier\":{\"key\":\"mine\",\"namespace\":\"\"},\"name\":\"My Items\",\"type\":\"folder\",\"composition\":[{\"key\":\"29836e66-111a-45f8-81ed-f662661be9f9\",\"namespace\":\"\"}],\"location\":\"ROOT\",\"modified\":1732413602140,\"created\":1732413600860,\"persisted\":1732413602140}}]" + "value": "[{\"objectPath\":[{\"identifier\":{\"key\":\"712d07f1-3585-465a-a6db-3c40a9edcde7\",\"namespace\":\"\"},\"name\":\"Parent Display Layout\",\"type\":\"layout\",\"composition\":[{\"key\":\"6731f693-b09e-46de-a0cb-9331f1fb2e6d\",\"namespace\":\"\"}],\"configuration\":{\"items\":[],\"layoutGrid\":[10,10]},\"notes\":\"framework/generateLocalStorageData.e2e.spec.js\\nGenerate Visual Test Data @localStorage @generatedata @clock\\nGenerate display layout with 1 child overlay plot\\nchrome\",\"modified\":1732413606392.7,\"location\":\"mine\",\"created\":1732413604993,\"persisted\":1732413606392.7},{\"identifier\":{\"key\":\"mine\",\"namespace\":\"\"},\"name\":\"My Items\",\"type\":\"folder\",\"composition\":[{\"key\":\"712d07f1-3585-465a-a6db-3c40a9edcde7\",\"namespace\":\"\"}],\"location\":\"ROOT\",\"modified\":1732413604993,\"created\":1732413603983,\"persisted\":1732413604993},{\"identifier\":{\"key\":\"ROOT\",\"namespace\":\"\"},\"name\":\"Open MCT\",\"type\":\"root\",\"composition\":[{\"key\":\"mine\",\"namespace\":\"\"}]}],\"navigationPath\":\"/browse/mine/712d07f1-3585-465a-a6db-3c40a9edcde7\",\"domainObject\":{\"identifier\":{\"key\":\"712d07f1-3585-465a-a6db-3c40a9edcde7\",\"namespace\":\"\"},\"name\":\"Parent Display Layout\",\"type\":\"layout\",\"composition\":[{\"key\":\"6731f693-b09e-46de-a0cb-9331f1fb2e6d\",\"namespace\":\"\"}],\"configuration\":{\"items\":[],\"layoutGrid\":[10,10]},\"notes\":\"framework/generateLocalStorageData.e2e.spec.js\\nGenerate Visual Test Data @localStorage @generatedata @clock\\nGenerate display layout with 1 child overlay plot\\nchrome\",\"modified\":1732413606392.7,\"location\":\"mine\",\"created\":1732413604993,\"persisted\":1732413606392.7}},{\"objectPath\":[{\"identifier\":{\"key\":\"fa050882-fcec-49c0-aa78-236a595accaf\",\"namespace\":\"\"},\"name\":\"Child SWG 1\",\"type\":\"generator\",\"telemetry\":{\"period\":10,\"amplitude\":1,\"offset\":0,\"dataRateInHz\":1,\"phase\":0,\"randomness\":0,\"loadDelay\":0,\"infinityValues\":false,\"exceedFloat32\":false,\"staleness\":false},\"notes\":\"framework/generateLocalStorageData.e2e.spec.js\\nGenerate Visual Test Data @localStorage @generatedata @clock\\nGenerate display layout with 1 child overlay plot\\nchrome\",\"modified\":1732413607788,\"location\":\"6731f693-b09e-46de-a0cb-9331f1fb2e6d\",\"created\":1732413607788,\"persisted\":1732413607788},{\"identifier\":{\"key\":\"6731f693-b09e-46de-a0cb-9331f1fb2e6d\",\"namespace\":\"\"},\"name\":\"Child Overlay Plot 1\",\"type\":\"telemetry.plot.overlay\",\"composition\":[{\"key\":\"fa050882-fcec-49c0-aa78-236a595accaf\",\"namespace\":\"\"}],\"configuration\":{\"series\":[]},\"notes\":\"framework/generateLocalStorageData.e2e.spec.js\\nGenerate Visual Test Data @localStorage @generatedata @clock\\nGenerate display layout with 1 child overlay plot\\nchrome\",\"modified\":1732413607788,\"location\":\"712d07f1-3585-465a-a6db-3c40a9edcde7\",\"created\":1732413606392.7,\"persisted\":1732413607788},{\"identifier\":{\"key\":\"712d07f1-3585-465a-a6db-3c40a9edcde7\",\"namespace\":\"\"},\"name\":\"Parent Display Layout\",\"type\":\"layout\",\"composition\":[{\"key\":\"6731f693-b09e-46de-a0cb-9331f1fb2e6d\",\"namespace\":\"\"}],\"configuration\":{\"items\":[],\"layoutGrid\":[10,10]},\"notes\":\"framework/generateLocalStorageData.e2e.spec.js\\nGenerate Visual Test Data @localStorage @generatedata @clock\\nGenerate display layout with 1 child overlay plot\\nchrome\",\"modified\":1732413606392.7,\"location\":\"mine\",\"created\":1732413604993,\"persisted\":1732413606392.7},{\"identifier\":{\"key\":\"mine\",\"namespace\":\"\"},\"name\":\"My Items\",\"type\":\"folder\",\"composition\":[{\"key\":\"712d07f1-3585-465a-a6db-3c40a9edcde7\",\"namespace\":\"\"}],\"location\":\"ROOT\",\"modified\":1732413604993,\"created\":1732413603983,\"persisted\":1732413604993},{\"identifier\":{\"key\":\"ROOT\",\"namespace\":\"\"},\"name\":\"Open MCT\",\"type\":\"root\",\"composition\":[{\"key\":\"mine\",\"namespace\":\"\"}]}],\"navigationPath\":\"/browse/mine/712d07f1-3585-465a-a6db-3c40a9edcde7/6731f693-b09e-46de-a0cb-9331f1fb2e6d/fa050882-fcec-49c0-aa78-236a595accaf\",\"domainObject\":{\"identifier\":{\"key\":\"fa050882-fcec-49c0-aa78-236a595accaf\",\"namespace\":\"\"},\"name\":\"Child SWG 1\",\"type\":\"generator\",\"telemetry\":{\"period\":10,\"amplitude\":1,\"offset\":0,\"dataRateInHz\":1,\"phase\":0,\"randomness\":0,\"loadDelay\":0,\"infinityValues\":false,\"exceedFloat32\":false,\"staleness\":false},\"notes\":\"framework/generateLocalStorageData.e2e.spec.js\\nGenerate Visual Test Data @localStorage @generatedata @clock\\nGenerate display layout with 1 child overlay plot\\nchrome\",\"modified\":1732413607788,\"location\":\"6731f693-b09e-46de-a0cb-9331f1fb2e6d\",\"created\":1732413607788,\"persisted\":1732413607788}},{\"objectPath\":[{\"identifier\":{\"key\":\"6731f693-b09e-46de-a0cb-9331f1fb2e6d\",\"namespace\":\"\"},\"name\":\"Child Overlay Plot 1\",\"type\":\"telemetry.plot.overlay\",\"composition\":[{\"key\":\"fa050882-fcec-49c0-aa78-236a595accaf\",\"namespace\":\"\"}],\"configuration\":{\"series\":[]},\"notes\":\"framework/generateLocalStorageData.e2e.spec.js\\nGenerate Visual Test Data @localStorage @generatedata @clock\\nGenerate display layout with 1 child overlay plot\\nchrome\",\"modified\":1732413607788,\"location\":\"712d07f1-3585-465a-a6db-3c40a9edcde7\",\"created\":1732413606392.7,\"persisted\":1732413607788},{\"identifier\":{\"key\":\"712d07f1-3585-465a-a6db-3c40a9edcde7\",\"namespace\":\"\"},\"name\":\"Parent Display Layout\",\"type\":\"layout\",\"composition\":[{\"key\":\"6731f693-b09e-46de-a0cb-9331f1fb2e6d\",\"namespace\":\"\"}],\"configuration\":{\"items\":[],\"layoutGrid\":[10,10]},\"notes\":\"framework/generateLocalStorageData.e2e.spec.js\\nGenerate Visual Test Data @localStorage @generatedata @clock\\nGenerate display layout with 1 child overlay plot\\nchrome\",\"modified\":1732413606392.7,\"location\":\"mine\",\"created\":1732413604993,\"persisted\":1732413606392.7},{\"identifier\":{\"key\":\"mine\",\"namespace\":\"\"},\"name\":\"My Items\",\"type\":\"folder\",\"composition\":[{\"key\":\"712d07f1-3585-465a-a6db-3c40a9edcde7\",\"namespace\":\"\"}],\"location\":\"ROOT\",\"modified\":1732413604993,\"created\":1732413603983,\"persisted\":1732413604993},{\"identifier\":{\"key\":\"ROOT\",\"namespace\":\"\"},\"name\":\"Open MCT\",\"type\":\"root\",\"composition\":[{\"key\":\"mine\",\"namespace\":\"\"}]}],\"navigationPath\":\"/browse/mine/712d07f1-3585-465a-a6db-3c40a9edcde7/6731f693-b09e-46de-a0cb-9331f1fb2e6d\",\"domainObject\":{\"identifier\":{\"key\":\"6731f693-b09e-46de-a0cb-9331f1fb2e6d\",\"namespace\":\"\"},\"name\":\"Child Overlay Plot 1\",\"type\":\"telemetry.plot.overlay\",\"composition\":[{\"key\":\"fa050882-fcec-49c0-aa78-236a595accaf\",\"namespace\":\"\"}],\"configuration\":{\"series\":[]},\"notes\":\"framework/generateLocalStorageData.e2e.spec.js\\nGenerate Visual Test Data @localStorage @generatedata @clock\\nGenerate display layout with 1 child overlay plot\\nchrome\",\"modified\":1732413607788,\"location\":\"712d07f1-3585-465a-a6db-3c40a9edcde7\",\"created\":1732413606392.7,\"persisted\":1732413607788}},{\"objectPath\":[{\"identifier\":{\"key\":\"mine\",\"namespace\":\"\"},\"name\":\"My Items\",\"type\":\"folder\",\"composition\":[{\"key\":\"712d07f1-3585-465a-a6db-3c40a9edcde7\",\"namespace\":\"\"}],\"location\":\"ROOT\",\"modified\":1732413604993,\"created\":1732413603983,\"persisted\":1732413604993},{\"identifier\":{\"key\":\"ROOT\",\"namespace\":\"\"},\"name\":\"Open MCT\",\"type\":\"root\",\"composition\":[{\"key\":\"mine\",\"namespace\":\"\"}]}],\"navigationPath\":\"/browse/mine\",\"domainObject\":{\"identifier\":{\"key\":\"mine\",\"namespace\":\"\"},\"name\":\"My Items\",\"type\":\"folder\",\"composition\":[{\"key\":\"712d07f1-3585-465a-a6db-3c40a9edcde7\",\"namespace\":\"\"}],\"location\":\"ROOT\",\"modified\":1732413604993,\"created\":1732413603983,\"persisted\":1732413604993}}]" } ] } diff --git a/e2e/test-data/flexible_layout_with_child_layouts.json b/e2e/test-data/flexible_layout_with_child_layouts.json index 696f2f7f37..0f3a18a8b8 100644 --- a/e2e/test-data/flexible_layout_with_child_layouts.json +++ b/e2e/test-data/flexible_layout_with_child_layouts.json @@ -6,15 +6,15 @@ "localStorage": [ { "name": "mct", - "value": "{\"mine\":{\"identifier\":{\"key\":\"mine\",\"namespace\":\"\"},\"name\":\"My Items\",\"type\":\"folder\",\"composition\":[{\"key\":\"a9ea2ec4-c456-4b73-8ed5-c4da07d40732\",\"namespace\":\"\"}],\"location\":\"ROOT\",\"modified\":1732413601660,\"created\":1732413600900,\"persisted\":1732413601660},\"a9ea2ec4-c456-4b73-8ed5-c4da07d40732\":{\"identifier\":{\"key\":\"a9ea2ec4-c456-4b73-8ed5-c4da07d40732\",\"namespace\":\"\"},\"name\":\"Parent Flexible Layout\",\"type\":\"flexible-layout\",\"configuration\":{\"containers\":[{\"id\":\"f7154ceb-c37b-40ee-af98-6b9cb8a3ec62\",\"frames\":[{\"id\":\"885b1828-fbf1-4f59-8c57-c5db7ad625b1\",\"domainObjectIdentifier\":{\"key\":\"bd6c95a1-31b2-4eaf-b962-f1b3da3de21a\",\"namespace\":\"\"},\"size\":100,\"noFrame\":false}],\"size\":50},{\"id\":\"f73ad0b3-c719-45e8-8056-a93eb7fea00f\",\"frames\":[],\"size\":50}],\"rowsLayout\":false},\"composition\":[{\"key\":\"bd6c95a1-31b2-4eaf-b962-f1b3da3de21a\",\"namespace\":\"\"},{\"key\":\"a0294124-ac2f-442b-9e6e-2086c6b0c2bd\",\"namespace\":\"\"}],\"notes\":\"framework/generateLocalStorageData.e2e.spec.js\\nGenerate Visual Test Data @localStorage @generatedata\\nGenerate flexible layout with 2 child display layouts\\nchrome\",\"modified\":1732413603920,\"location\":\"mine\",\"created\":1732413601660,\"persisted\":1732413603920},\"bd6c95a1-31b2-4eaf-b962-f1b3da3de21a\":{\"name\":\"Child Layout 1\",\"type\":\"layout\",\"identifier\":{\"key\":\"bd6c95a1-31b2-4eaf-b962-f1b3da3de21a\",\"namespace\":\"\"},\"composition\":[],\"configuration\":{\"items\":[],\"layoutGrid\":[10,10]},\"notes\":\"framework/generateLocalStorageData.e2e.spec.js\\nGenerate Visual Test Data @localStorage @generatedata\\nGenerate flexible layout with 2 child display layouts\\nchrome\",\"modified\":1732413602800,\"location\":\"a9ea2ec4-c456-4b73-8ed5-c4da07d40732\",\"created\":1732413602800,\"persisted\":1732413602800},\"a0294124-ac2f-442b-9e6e-2086c6b0c2bd\":{\"name\":\"Child Layout 2\",\"type\":\"layout\",\"identifier\":{\"key\":\"a0294124-ac2f-442b-9e6e-2086c6b0c2bd\",\"namespace\":\"\"},\"composition\":[],\"configuration\":{\"items\":[],\"layoutGrid\":[10,10]},\"notes\":\"framework/generateLocalStorageData.e2e.spec.js\\nGenerate Visual Test Data @localStorage @generatedata\\nGenerate flexible layout with 2 child display layouts\\nchrome\",\"modified\":1732413603920,\"location\":\"a9ea2ec4-c456-4b73-8ed5-c4da07d40732\",\"created\":1732413603920,\"persisted\":1732413603920}}" - }, - { - "name": "mct-recent-objects", - "value": "[{\"objectPath\":[{\"identifier\":{\"key\":\"a9ea2ec4-c456-4b73-8ed5-c4da07d40732\",\"namespace\":\"\"},\"name\":\"Parent Flexible Layout\",\"type\":\"flexible-layout\",\"configuration\":{\"containers\":[{\"id\":\"f7154ceb-c37b-40ee-af98-6b9cb8a3ec62\",\"frames\":[{\"id\":\"885b1828-fbf1-4f59-8c57-c5db7ad625b1\",\"domainObjectIdentifier\":{\"key\":\"bd6c95a1-31b2-4eaf-b962-f1b3da3de21a\",\"namespace\":\"\"},\"size\":100,\"noFrame\":false}],\"size\":50},{\"id\":\"f73ad0b3-c719-45e8-8056-a93eb7fea00f\",\"frames\":[],\"size\":50}],\"rowsLayout\":false},\"composition\":[{\"key\":\"bd6c95a1-31b2-4eaf-b962-f1b3da3de21a\",\"namespace\":\"\"},{\"key\":\"a0294124-ac2f-442b-9e6e-2086c6b0c2bd\",\"namespace\":\"\"}],\"notes\":\"framework/generateLocalStorageData.e2e.spec.js\\nGenerate Visual Test Data @localStorage @generatedata\\nGenerate flexible layout with 2 child display layouts\\nchrome\",\"modified\":1732413603920,\"location\":\"mine\",\"created\":1732413601660,\"persisted\":1732413603920},{\"identifier\":{\"key\":\"mine\",\"namespace\":\"\"},\"name\":\"My Items\",\"type\":\"folder\",\"composition\":[{\"key\":\"a9ea2ec4-c456-4b73-8ed5-c4da07d40732\",\"namespace\":\"\"}],\"location\":\"ROOT\",\"modified\":1732413601660,\"created\":1732413600900,\"persisted\":1732413601660},{\"identifier\":{\"key\":\"ROOT\",\"namespace\":\"\"},\"name\":\"Open MCT\",\"type\":\"root\",\"composition\":[{\"key\":\"mine\",\"namespace\":\"\"}]}],\"navigationPath\":\"/browse/mine/a9ea2ec4-c456-4b73-8ed5-c4da07d40732\",\"domainObject\":{\"identifier\":{\"key\":\"a9ea2ec4-c456-4b73-8ed5-c4da07d40732\",\"namespace\":\"\"},\"name\":\"Parent Flexible Layout\",\"type\":\"flexible-layout\",\"configuration\":{\"containers\":[{\"id\":\"f7154ceb-c37b-40ee-af98-6b9cb8a3ec62\",\"frames\":[{\"id\":\"885b1828-fbf1-4f59-8c57-c5db7ad625b1\",\"domainObjectIdentifier\":{\"key\":\"bd6c95a1-31b2-4eaf-b962-f1b3da3de21a\",\"namespace\":\"\"},\"size\":100,\"noFrame\":false}],\"size\":50},{\"id\":\"f73ad0b3-c719-45e8-8056-a93eb7fea00f\",\"frames\":[],\"size\":50}],\"rowsLayout\":false},\"composition\":[{\"key\":\"bd6c95a1-31b2-4eaf-b962-f1b3da3de21a\",\"namespace\":\"\"},{\"key\":\"a0294124-ac2f-442b-9e6e-2086c6b0c2bd\",\"namespace\":\"\"}],\"notes\":\"framework/generateLocalStorageData.e2e.spec.js\\nGenerate Visual Test Data @localStorage @generatedata\\nGenerate flexible layout with 2 child display layouts\\nchrome\",\"modified\":1732413603920,\"location\":\"mine\",\"created\":1732413601660,\"persisted\":1732413603920}},{\"objectPath\":[{\"identifier\":{\"key\":\"a0294124-ac2f-442b-9e6e-2086c6b0c2bd\",\"namespace\":\"\"},\"name\":\"Child Layout 2\",\"type\":\"layout\",\"composition\":[],\"configuration\":{\"items\":[],\"layoutGrid\":[10,10]},\"notes\":\"framework/generateLocalStorageData.e2e.spec.js\\nGenerate Visual Test Data @localStorage @generatedata\\nGenerate flexible layout with 2 child display layouts\\nchrome\",\"modified\":1732413603920,\"location\":\"a9ea2ec4-c456-4b73-8ed5-c4da07d40732\",\"created\":1732413603920,\"persisted\":1732413603920},{\"identifier\":{\"key\":\"a9ea2ec4-c456-4b73-8ed5-c4da07d40732\",\"namespace\":\"\"},\"name\":\"Parent Flexible Layout\",\"type\":\"flexible-layout\",\"configuration\":{\"containers\":[{\"id\":\"f7154ceb-c37b-40ee-af98-6b9cb8a3ec62\",\"frames\":[{\"id\":\"885b1828-fbf1-4f59-8c57-c5db7ad625b1\",\"domainObjectIdentifier\":{\"key\":\"bd6c95a1-31b2-4eaf-b962-f1b3da3de21a\",\"namespace\":\"\"},\"size\":100,\"noFrame\":false}],\"size\":50},{\"id\":\"f73ad0b3-c719-45e8-8056-a93eb7fea00f\",\"frames\":[],\"size\":50}],\"rowsLayout\":false},\"composition\":[{\"key\":\"bd6c95a1-31b2-4eaf-b962-f1b3da3de21a\",\"namespace\":\"\"},{\"key\":\"a0294124-ac2f-442b-9e6e-2086c6b0c2bd\",\"namespace\":\"\"}],\"notes\":\"framework/generateLocalStorageData.e2e.spec.js\\nGenerate Visual Test Data @localStorage @generatedata\\nGenerate flexible layout with 2 child display layouts\\nchrome\",\"modified\":1732413603920,\"location\":\"mine\",\"created\":1732413601660,\"persisted\":1732413603920},{\"identifier\":{\"key\":\"mine\",\"namespace\":\"\"},\"name\":\"My Items\",\"type\":\"folder\",\"composition\":[{\"key\":\"a9ea2ec4-c456-4b73-8ed5-c4da07d40732\",\"namespace\":\"\"}],\"location\":\"ROOT\",\"modified\":1732413601660,\"created\":1732413600900,\"persisted\":1732413601660},{\"identifier\":{\"key\":\"ROOT\",\"namespace\":\"\"},\"name\":\"Open MCT\",\"type\":\"root\",\"composition\":[{\"key\":\"mine\",\"namespace\":\"\"}]}],\"navigationPath\":\"/browse/mine/a9ea2ec4-c456-4b73-8ed5-c4da07d40732/a0294124-ac2f-442b-9e6e-2086c6b0c2bd\",\"domainObject\":{\"identifier\":{\"key\":\"a0294124-ac2f-442b-9e6e-2086c6b0c2bd\",\"namespace\":\"\"},\"name\":\"Child Layout 2\",\"type\":\"layout\",\"composition\":[],\"configuration\":{\"items\":[],\"layoutGrid\":[10,10]},\"notes\":\"framework/generateLocalStorageData.e2e.spec.js\\nGenerate Visual Test Data @localStorage @generatedata\\nGenerate flexible layout with 2 child display layouts\\nchrome\",\"modified\":1732413603920,\"location\":\"a9ea2ec4-c456-4b73-8ed5-c4da07d40732\",\"created\":1732413603920,\"persisted\":1732413603920}},{\"objectPath\":[{\"identifier\":{\"key\":\"bd6c95a1-31b2-4eaf-b962-f1b3da3de21a\",\"namespace\":\"\"},\"name\":\"Child Layout 1\",\"type\":\"layout\",\"composition\":[],\"configuration\":{\"items\":[],\"layoutGrid\":[10,10]},\"notes\":\"framework/generateLocalStorageData.e2e.spec.js\\nGenerate Visual Test Data @localStorage @generatedata\\nGenerate flexible layout with 2 child display layouts\\nchrome\",\"modified\":1732413602800,\"location\":\"a9ea2ec4-c456-4b73-8ed5-c4da07d40732\",\"created\":1732413602800,\"persisted\":1732413602800},{\"identifier\":{\"key\":\"a9ea2ec4-c456-4b73-8ed5-c4da07d40732\",\"namespace\":\"\"},\"name\":\"Parent Flexible Layout\",\"type\":\"flexible-layout\",\"configuration\":{\"containers\":[{\"id\":\"f7154ceb-c37b-40ee-af98-6b9cb8a3ec62\",\"frames\":[{\"id\":\"885b1828-fbf1-4f59-8c57-c5db7ad625b1\",\"domainObjectIdentifier\":{\"key\":\"bd6c95a1-31b2-4eaf-b962-f1b3da3de21a\",\"namespace\":\"\"},\"size\":100,\"noFrame\":false}],\"size\":50},{\"id\":\"f73ad0b3-c719-45e8-8056-a93eb7fea00f\",\"frames\":[],\"size\":50}],\"rowsLayout\":false},\"composition\":[{\"key\":\"bd6c95a1-31b2-4eaf-b962-f1b3da3de21a\",\"namespace\":\"\"},{\"key\":\"a0294124-ac2f-442b-9e6e-2086c6b0c2bd\",\"namespace\":\"\"}],\"notes\":\"framework/generateLocalStorageData.e2e.spec.js\\nGenerate Visual Test Data @localStorage @generatedata\\nGenerate flexible layout with 2 child display layouts\\nchrome\",\"modified\":1732413603920,\"location\":\"mine\",\"created\":1732413601660,\"persisted\":1732413603920},{\"identifier\":{\"key\":\"mine\",\"namespace\":\"\"},\"name\":\"My Items\",\"type\":\"folder\",\"composition\":[{\"key\":\"a9ea2ec4-c456-4b73-8ed5-c4da07d40732\",\"namespace\":\"\"}],\"location\":\"ROOT\",\"modified\":1732413601660,\"created\":1732413600900,\"persisted\":1732413601660},{\"identifier\":{\"key\":\"ROOT\",\"namespace\":\"\"},\"name\":\"Open MCT\",\"type\":\"root\",\"composition\":[{\"key\":\"mine\",\"namespace\":\"\"}]}],\"navigationPath\":\"/browse/mine/a9ea2ec4-c456-4b73-8ed5-c4da07d40732/bd6c95a1-31b2-4eaf-b962-f1b3da3de21a\",\"domainObject\":{\"identifier\":{\"key\":\"bd6c95a1-31b2-4eaf-b962-f1b3da3de21a\",\"namespace\":\"\"},\"name\":\"Child Layout 1\",\"type\":\"layout\",\"composition\":[],\"configuration\":{\"items\":[],\"layoutGrid\":[10,10]},\"notes\":\"framework/generateLocalStorageData.e2e.spec.js\\nGenerate Visual Test Data @localStorage @generatedata\\nGenerate flexible layout with 2 child display layouts\\nchrome\",\"modified\":1732413602800,\"location\":\"a9ea2ec4-c456-4b73-8ed5-c4da07d40732\",\"created\":1732413602800,\"persisted\":1732413602800}},{\"objectPath\":[{\"identifier\":{\"key\":\"mine\",\"namespace\":\"\"},\"name\":\"My Items\",\"type\":\"folder\",\"composition\":[{\"key\":\"a9ea2ec4-c456-4b73-8ed5-c4da07d40732\",\"namespace\":\"\"}],\"location\":\"ROOT\",\"modified\":1732413601660,\"created\":1732413600900,\"persisted\":1732413601660},{\"identifier\":{\"key\":\"ROOT\",\"namespace\":\"\"},\"name\":\"Open MCT\",\"type\":\"root\",\"composition\":[{\"key\":\"mine\",\"namespace\":\"\"}]}],\"navigationPath\":\"/browse/mine\",\"domainObject\":{\"identifier\":{\"key\":\"mine\",\"namespace\":\"\"},\"name\":\"My Items\",\"type\":\"folder\",\"composition\":[{\"key\":\"a9ea2ec4-c456-4b73-8ed5-c4da07d40732\",\"namespace\":\"\"}],\"location\":\"ROOT\",\"modified\":1732413601660,\"created\":1732413600900,\"persisted\":1732413601660}}]" + "value": "{\"mine\":{\"identifier\":{\"key\":\"mine\",\"namespace\":\"\"},\"name\":\"My Items\",\"type\":\"folder\",\"composition\":[{\"key\":\"3b2f155b-ec69-48d5-ac58-76af10187a35\",\"namespace\":\"\"}],\"location\":\"ROOT\",\"modified\":1732413605725.7,\"created\":1732413604014,\"persisted\":1732413605725.7},\"3b2f155b-ec69-48d5-ac58-76af10187a35\":{\"identifier\":{\"key\":\"3b2f155b-ec69-48d5-ac58-76af10187a35\",\"namespace\":\"\"},\"name\":\"Parent Flexible Layout\",\"type\":\"flexible-layout\",\"configuration\":{\"containers\":[{\"id\":\"344ecaa0-d7f6-45de-a5bc-74bc3575e529\",\"frames\":[{\"id\":\"928f4a48-d080-4983-b3b0-8089b925c702\",\"domainObjectIdentifier\":{\"key\":\"a6141275-b3a0-4768-8e4a-197f1cb809dd\",\"namespace\":\"\"},\"size\":100,\"noFrame\":false}],\"size\":50},{\"id\":\"de887070-1474-4ce6-8863-8002926d745e\",\"frames\":[],\"size\":50}],\"rowsLayout\":false},\"composition\":[{\"key\":\"a6141275-b3a0-4768-8e4a-197f1cb809dd\",\"namespace\":\"\"},{\"key\":\"20cad719-b961-457c-abcf-262944bdd1ce\",\"namespace\":\"\"}],\"notes\":\"framework/generateLocalStorageData.e2e.spec.js\\nGenerate Visual Test Data @localStorage @generatedata @clock\\nGenerate flexible layout with 2 child display layouts\\nchrome\",\"modified\":1732413607918,\"location\":\"mine\",\"created\":1732413605725.7,\"persisted\":1732413607918},\"a6141275-b3a0-4768-8e4a-197f1cb809dd\":{\"name\":\"Child Layout 1\",\"type\":\"layout\",\"identifier\":{\"key\":\"a6141275-b3a0-4768-8e4a-197f1cb809dd\",\"namespace\":\"\"},\"composition\":[],\"configuration\":{\"items\":[],\"layoutGrid\":[10,10]},\"notes\":\"framework/generateLocalStorageData.e2e.spec.js\\nGenerate Visual Test Data @localStorage @generatedata @clock\\nGenerate flexible layout with 2 child display layouts\\nchrome\",\"modified\":1732413606530,\"location\":\"3b2f155b-ec69-48d5-ac58-76af10187a35\",\"created\":1732413606530,\"persisted\":1732413606530},\"20cad719-b961-457c-abcf-262944bdd1ce\":{\"name\":\"Child Layout 2\",\"type\":\"layout\",\"identifier\":{\"key\":\"20cad719-b961-457c-abcf-262944bdd1ce\",\"namespace\":\"\"},\"composition\":[],\"configuration\":{\"items\":[],\"layoutGrid\":[10,10]},\"notes\":\"framework/generateLocalStorageData.e2e.spec.js\\nGenerate Visual Test Data @localStorage @generatedata @clock\\nGenerate flexible layout with 2 child display layouts\\nchrome\",\"modified\":1732413607918,\"location\":\"3b2f155b-ec69-48d5-ac58-76af10187a35\",\"created\":1732413607918,\"persisted\":1732413607918}}" }, { "name": "mct-tree-expanded", "value": "[]" + }, + { + "name": "mct-recent-objects", + "value": "[{\"objectPath\":[{\"identifier\":{\"key\":\"3b2f155b-ec69-48d5-ac58-76af10187a35\",\"namespace\":\"\"},\"name\":\"Parent Flexible Layout\",\"type\":\"flexible-layout\",\"configuration\":{\"containers\":[{\"id\":\"344ecaa0-d7f6-45de-a5bc-74bc3575e529\",\"frames\":[{\"id\":\"928f4a48-d080-4983-b3b0-8089b925c702\",\"domainObjectIdentifier\":{\"key\":\"a6141275-b3a0-4768-8e4a-197f1cb809dd\",\"namespace\":\"\"},\"size\":100,\"noFrame\":false}],\"size\":50},{\"id\":\"de887070-1474-4ce6-8863-8002926d745e\",\"frames\":[],\"size\":50}],\"rowsLayout\":false},\"composition\":[{\"key\":\"a6141275-b3a0-4768-8e4a-197f1cb809dd\",\"namespace\":\"\"},{\"key\":\"20cad719-b961-457c-abcf-262944bdd1ce\",\"namespace\":\"\"}],\"notes\":\"framework/generateLocalStorageData.e2e.spec.js\\nGenerate Visual Test Data @localStorage @generatedata @clock\\nGenerate flexible layout with 2 child display layouts\\nchrome\",\"modified\":1732413607918,\"location\":\"mine\",\"created\":1732413605725.7,\"persisted\":1732413607918},{\"identifier\":{\"key\":\"mine\",\"namespace\":\"\"},\"name\":\"My Items\",\"type\":\"folder\",\"composition\":[{\"key\":\"3b2f155b-ec69-48d5-ac58-76af10187a35\",\"namespace\":\"\"}],\"location\":\"ROOT\",\"modified\":1732413605725.7,\"created\":1732413604014,\"persisted\":1732413605725.7},{\"identifier\":{\"key\":\"ROOT\",\"namespace\":\"\"},\"name\":\"Open MCT\",\"type\":\"root\",\"composition\":[{\"key\":\"mine\",\"namespace\":\"\"}]}],\"navigationPath\":\"/browse/mine/3b2f155b-ec69-48d5-ac58-76af10187a35\",\"domainObject\":{\"identifier\":{\"key\":\"3b2f155b-ec69-48d5-ac58-76af10187a35\",\"namespace\":\"\"},\"name\":\"Parent Flexible Layout\",\"type\":\"flexible-layout\",\"configuration\":{\"containers\":[{\"id\":\"344ecaa0-d7f6-45de-a5bc-74bc3575e529\",\"frames\":[{\"id\":\"928f4a48-d080-4983-b3b0-8089b925c702\",\"domainObjectIdentifier\":{\"key\":\"a6141275-b3a0-4768-8e4a-197f1cb809dd\",\"namespace\":\"\"},\"size\":100,\"noFrame\":false}],\"size\":50},{\"id\":\"de887070-1474-4ce6-8863-8002926d745e\",\"frames\":[],\"size\":50}],\"rowsLayout\":false},\"composition\":[{\"key\":\"a6141275-b3a0-4768-8e4a-197f1cb809dd\",\"namespace\":\"\"},{\"key\":\"20cad719-b961-457c-abcf-262944bdd1ce\",\"namespace\":\"\"}],\"notes\":\"framework/generateLocalStorageData.e2e.spec.js\\nGenerate Visual Test Data @localStorage @generatedata @clock\\nGenerate flexible layout with 2 child display layouts\\nchrome\",\"modified\":1732413607918,\"location\":\"mine\",\"created\":1732413605725.7,\"persisted\":1732413607918}},{\"objectPath\":[{\"identifier\":{\"key\":\"20cad719-b961-457c-abcf-262944bdd1ce\",\"namespace\":\"\"},\"name\":\"Child Layout 2\",\"type\":\"layout\",\"composition\":[],\"configuration\":{\"items\":[],\"layoutGrid\":[10,10]},\"notes\":\"framework/generateLocalStorageData.e2e.spec.js\\nGenerate Visual Test Data @localStorage @generatedata @clock\\nGenerate flexible layout with 2 child display layouts\\nchrome\",\"modified\":1732413607918,\"location\":\"3b2f155b-ec69-48d5-ac58-76af10187a35\",\"created\":1732413607918,\"persisted\":1732413607918},{\"identifier\":{\"key\":\"3b2f155b-ec69-48d5-ac58-76af10187a35\",\"namespace\":\"\"},\"name\":\"Parent Flexible Layout\",\"type\":\"flexible-layout\",\"configuration\":{\"containers\":[{\"id\":\"344ecaa0-d7f6-45de-a5bc-74bc3575e529\",\"frames\":[{\"id\":\"928f4a48-d080-4983-b3b0-8089b925c702\",\"domainObjectIdentifier\":{\"key\":\"a6141275-b3a0-4768-8e4a-197f1cb809dd\",\"namespace\":\"\"},\"size\":100,\"noFrame\":false}],\"size\":50},{\"id\":\"de887070-1474-4ce6-8863-8002926d745e\",\"frames\":[],\"size\":50}],\"rowsLayout\":false},\"composition\":[{\"key\":\"a6141275-b3a0-4768-8e4a-197f1cb809dd\",\"namespace\":\"\"},{\"key\":\"20cad719-b961-457c-abcf-262944bdd1ce\",\"namespace\":\"\"}],\"notes\":\"framework/generateLocalStorageData.e2e.spec.js\\nGenerate Visual Test Data @localStorage @generatedata @clock\\nGenerate flexible layout with 2 child display layouts\\nchrome\",\"modified\":1732413607918,\"location\":\"mine\",\"created\":1732413605725.7,\"persisted\":1732413607918},{\"identifier\":{\"key\":\"mine\",\"namespace\":\"\"},\"name\":\"My Items\",\"type\":\"folder\",\"composition\":[{\"key\":\"3b2f155b-ec69-48d5-ac58-76af10187a35\",\"namespace\":\"\"}],\"location\":\"ROOT\",\"modified\":1732413605725.7,\"created\":1732413604014,\"persisted\":1732413605725.7},{\"identifier\":{\"key\":\"ROOT\",\"namespace\":\"\"},\"name\":\"Open MCT\",\"type\":\"root\",\"composition\":[{\"key\":\"mine\",\"namespace\":\"\"}]}],\"navigationPath\":\"/browse/mine/3b2f155b-ec69-48d5-ac58-76af10187a35/20cad719-b961-457c-abcf-262944bdd1ce\",\"domainObject\":{\"identifier\":{\"key\":\"20cad719-b961-457c-abcf-262944bdd1ce\",\"namespace\":\"\"},\"name\":\"Child Layout 2\",\"type\":\"layout\",\"composition\":[],\"configuration\":{\"items\":[],\"layoutGrid\":[10,10]},\"notes\":\"framework/generateLocalStorageData.e2e.spec.js\\nGenerate Visual Test Data @localStorage @generatedata @clock\\nGenerate flexible layout with 2 child display layouts\\nchrome\",\"modified\":1732413607918,\"location\":\"3b2f155b-ec69-48d5-ac58-76af10187a35\",\"created\":1732413607918,\"persisted\":1732413607918}},{\"objectPath\":[{\"identifier\":{\"key\":\"a6141275-b3a0-4768-8e4a-197f1cb809dd\",\"namespace\":\"\"},\"name\":\"Child Layout 1\",\"type\":\"layout\",\"composition\":[],\"configuration\":{\"items\":[],\"layoutGrid\":[10,10]},\"notes\":\"framework/generateLocalStorageData.e2e.spec.js\\nGenerate Visual Test Data @localStorage @generatedata @clock\\nGenerate flexible layout with 2 child display layouts\\nchrome\",\"modified\":1732413606530,\"location\":\"3b2f155b-ec69-48d5-ac58-76af10187a35\",\"created\":1732413606530,\"persisted\":1732413606530},{\"identifier\":{\"key\":\"3b2f155b-ec69-48d5-ac58-76af10187a35\",\"namespace\":\"\"},\"name\":\"Parent Flexible Layout\",\"type\":\"flexible-layout\",\"configuration\":{\"containers\":[{\"id\":\"344ecaa0-d7f6-45de-a5bc-74bc3575e529\",\"frames\":[{\"id\":\"928f4a48-d080-4983-b3b0-8089b925c702\",\"domainObjectIdentifier\":{\"key\":\"a6141275-b3a0-4768-8e4a-197f1cb809dd\",\"namespace\":\"\"},\"size\":100,\"noFrame\":false}],\"size\":50},{\"id\":\"de887070-1474-4ce6-8863-8002926d745e\",\"frames\":[],\"size\":50}],\"rowsLayout\":false},\"composition\":[{\"key\":\"a6141275-b3a0-4768-8e4a-197f1cb809dd\",\"namespace\":\"\"},{\"key\":\"20cad719-b961-457c-abcf-262944bdd1ce\",\"namespace\":\"\"}],\"notes\":\"framework/generateLocalStorageData.e2e.spec.js\\nGenerate Visual Test Data @localStorage @generatedata @clock\\nGenerate flexible layout with 2 child display layouts\\nchrome\",\"modified\":1732413607918,\"location\":\"mine\",\"created\":1732413605725.7,\"persisted\":1732413607918},{\"identifier\":{\"key\":\"mine\",\"namespace\":\"\"},\"name\":\"My Items\",\"type\":\"folder\",\"composition\":[{\"key\":\"3b2f155b-ec69-48d5-ac58-76af10187a35\",\"namespace\":\"\"}],\"location\":\"ROOT\",\"modified\":1732413605725.7,\"created\":1732413604014,\"persisted\":1732413605725.7},{\"identifier\":{\"key\":\"ROOT\",\"namespace\":\"\"},\"name\":\"Open MCT\",\"type\":\"root\",\"composition\":[{\"key\":\"mine\",\"namespace\":\"\"}]}],\"navigationPath\":\"/browse/mine/3b2f155b-ec69-48d5-ac58-76af10187a35/a6141275-b3a0-4768-8e4a-197f1cb809dd\",\"domainObject\":{\"identifier\":{\"key\":\"a6141275-b3a0-4768-8e4a-197f1cb809dd\",\"namespace\":\"\"},\"name\":\"Child Layout 1\",\"type\":\"layout\",\"composition\":[],\"configuration\":{\"items\":[],\"layoutGrid\":[10,10]},\"notes\":\"framework/generateLocalStorageData.e2e.spec.js\\nGenerate Visual Test Data @localStorage @generatedata @clock\\nGenerate flexible layout with 2 child display layouts\\nchrome\",\"modified\":1732413606530,\"location\":\"3b2f155b-ec69-48d5-ac58-76af10187a35\",\"created\":1732413606530,\"persisted\":1732413606530}},{\"objectPath\":[{\"identifier\":{\"key\":\"mine\",\"namespace\":\"\"},\"name\":\"My Items\",\"type\":\"folder\",\"composition\":[{\"key\":\"3b2f155b-ec69-48d5-ac58-76af10187a35\",\"namespace\":\"\"}],\"location\":\"ROOT\",\"modified\":1732413605725.7,\"created\":1732413604014,\"persisted\":1732413605725.7},{\"identifier\":{\"key\":\"ROOT\",\"namespace\":\"\"},\"name\":\"Open MCT\",\"type\":\"root\",\"composition\":[{\"key\":\"mine\",\"namespace\":\"\"}]}],\"navigationPath\":\"/browse/mine\",\"domainObject\":{\"identifier\":{\"key\":\"mine\",\"namespace\":\"\"},\"name\":\"My Items\",\"type\":\"folder\",\"composition\":[{\"key\":\"3b2f155b-ec69-48d5-ac58-76af10187a35\",\"namespace\":\"\"}],\"location\":\"ROOT\",\"modified\":1732413605725.7,\"created\":1732413604014,\"persisted\":1732413605725.7}}]" } ] } diff --git a/e2e/test-data/overlay_plot_storage.json b/e2e/test-data/overlay_plot_storage.json index 310795dbc0..947c4119be 100644 --- a/e2e/test-data/overlay_plot_storage.json +++ b/e2e/test-data/overlay_plot_storage.json @@ -6,15 +6,15 @@ "localStorage": [ { "name": "mct", - "value": "{\"mine\":{\"identifier\":{\"key\":\"mine\",\"namespace\":\"\"},\"name\":\"My Items\",\"type\":\"folder\",\"composition\":[{\"key\":\"e78ca721-fb5e-409b-bf6d-597c87cb716f\",\"namespace\":\"\"},{\"key\":\"c6100044-56be-44b3-acca-6b9fddfb3849\",\"namespace\":\"\"}],\"location\":\"ROOT\",\"modified\":1732413602460,\"created\":1732413600960,\"persisted\":1732413602460},\"e78ca721-fb5e-409b-bf6d-597c87cb716f\":{\"identifier\":{\"key\":\"e78ca721-fb5e-409b-bf6d-597c87cb716f\",\"namespace\":\"\"},\"name\":\"Overlay Plot with Telemetry Object\",\"type\":\"telemetry.plot.overlay\",\"composition\":[{\"key\":\"c6100044-56be-44b3-acca-6b9fddfb3849\",\"namespace\":\"\"}],\"configuration\":{\"series\":[{\"identifier\":{\"key\":\"c6100044-56be-44b3-acca-6b9fddfb3849\",\"namespace\":\"\"}}]},\"notes\":\"framework/generateLocalStorageData.e2e.spec.js\\nGenerate Visual Test Data @localStorage @generatedata\\nGenerate Overlay Plot with Telemetry Object\\nchrome\",\"modified\":1732413603880,\"location\":\"mine\",\"created\":1732413601740,\"persisted\":1732413603880},\"c6100044-56be-44b3-acca-6b9fddfb3849\":{\"name\":\"VIPER Rover Heading\",\"type\":\"generator\",\"identifier\":{\"key\":\"c6100044-56be-44b3-acca-6b9fddfb3849\",\"namespace\":\"\"},\"telemetry\":{\"period\":10,\"amplitude\":1,\"offset\":0,\"dataRateInHz\":1,\"phase\":0,\"randomness\":0,\"loadDelay\":0,\"infinityValues\":false,\"exceedFloat32\":false,\"staleness\":false},\"modified\":1732413602460,\"location\":\"mine\",\"created\":1732413602460,\"persisted\":1732413602460}}" - }, - { - "name": "mct-recent-objects", - "value": "[{\"objectPath\":[{\"identifier\":{\"key\":\"c6100044-56be-44b3-acca-6b9fddfb3849\",\"namespace\":\"\"},\"name\":\"VIPER Rover Heading\",\"type\":\"generator\",\"telemetry\":{\"period\":10,\"amplitude\":1,\"offset\":0,\"dataRateInHz\":1,\"phase\":0,\"randomness\":0,\"loadDelay\":0,\"infinityValues\":false,\"exceedFloat32\":false,\"staleness\":false},\"modified\":1732413602460,\"location\":\"mine\",\"created\":1732413602460,\"persisted\":1732413602460},{\"identifier\":{\"key\":\"mine\",\"namespace\":\"\"},\"name\":\"My Items\",\"type\":\"folder\",\"composition\":[{\"key\":\"e78ca721-fb5e-409b-bf6d-597c87cb716f\",\"namespace\":\"\"},{\"key\":\"c6100044-56be-44b3-acca-6b9fddfb3849\",\"namespace\":\"\"}],\"location\":\"ROOT\",\"modified\":1732413602460,\"created\":1732413600960,\"persisted\":1732413602460},{\"identifier\":{\"key\":\"ROOT\",\"namespace\":\"\"},\"name\":\"Open MCT\",\"type\":\"root\",\"composition\":[{\"key\":\"mine\",\"namespace\":\"\"}]}],\"navigationPath\":\"/browse/mine/c6100044-56be-44b3-acca-6b9fddfb3849\",\"domainObject\":{\"identifier\":{\"key\":\"c6100044-56be-44b3-acca-6b9fddfb3849\",\"namespace\":\"\"},\"name\":\"VIPER Rover Heading\",\"type\":\"generator\",\"telemetry\":{\"period\":10,\"amplitude\":1,\"offset\":0,\"dataRateInHz\":1,\"phase\":0,\"randomness\":0,\"loadDelay\":0,\"infinityValues\":false,\"exceedFloat32\":false,\"staleness\":false},\"modified\":1732413602460,\"location\":\"mine\",\"created\":1732413602460,\"persisted\":1732413602460}},{\"objectPath\":[{\"identifier\":{\"key\":\"e78ca721-fb5e-409b-bf6d-597c87cb716f\",\"namespace\":\"\"},\"name\":\"Overlay Plot with Telemetry Object\",\"type\":\"telemetry.plot.overlay\",\"composition\":[{\"key\":\"c6100044-56be-44b3-acca-6b9fddfb3849\",\"namespace\":\"\"}],\"configuration\":{\"series\":[{\"identifier\":{\"key\":\"c6100044-56be-44b3-acca-6b9fddfb3849\",\"namespace\":\"\"}}]},\"notes\":\"framework/generateLocalStorageData.e2e.spec.js\\nGenerate Visual Test Data @localStorage @generatedata\\nGenerate Overlay Plot with Telemetry Object\\nchrome\",\"modified\":1732413603880,\"location\":\"mine\",\"created\":1732413601740,\"persisted\":1732413603880},{\"identifier\":{\"key\":\"mine\",\"namespace\":\"\"},\"name\":\"My Items\",\"type\":\"folder\",\"composition\":[{\"key\":\"e78ca721-fb5e-409b-bf6d-597c87cb716f\",\"namespace\":\"\"},{\"key\":\"c6100044-56be-44b3-acca-6b9fddfb3849\",\"namespace\":\"\"}],\"location\":\"ROOT\",\"modified\":1732413602460,\"created\":1732413600960,\"persisted\":1732413602460},{\"identifier\":{\"key\":\"ROOT\",\"namespace\":\"\"},\"name\":\"Open MCT\",\"type\":\"root\",\"composition\":[{\"key\":\"mine\",\"namespace\":\"\"}]}],\"navigationPath\":\"/browse/mine/e78ca721-fb5e-409b-bf6d-597c87cb716f\",\"domainObject\":{\"identifier\":{\"key\":\"e78ca721-fb5e-409b-bf6d-597c87cb716f\",\"namespace\":\"\"},\"name\":\"Overlay Plot with Telemetry Object\",\"type\":\"telemetry.plot.overlay\",\"composition\":[{\"key\":\"c6100044-56be-44b3-acca-6b9fddfb3849\",\"namespace\":\"\"}],\"configuration\":{\"series\":[{\"identifier\":{\"key\":\"c6100044-56be-44b3-acca-6b9fddfb3849\",\"namespace\":\"\"}}]},\"notes\":\"framework/generateLocalStorageData.e2e.spec.js\\nGenerate Visual Test Data @localStorage @generatedata\\nGenerate Overlay Plot with Telemetry Object\\nchrome\",\"modified\":1732413603880,\"location\":\"mine\",\"created\":1732413601740,\"persisted\":1732413603880}},{\"objectPath\":[{\"identifier\":{\"key\":\"mine\",\"namespace\":\"\"},\"name\":\"My Items\",\"type\":\"folder\",\"composition\":[{\"key\":\"e78ca721-fb5e-409b-bf6d-597c87cb716f\",\"namespace\":\"\"},{\"key\":\"c6100044-56be-44b3-acca-6b9fddfb3849\",\"namespace\":\"\"}],\"location\":\"ROOT\",\"modified\":1732413602460,\"created\":1732413600960,\"persisted\":1732413602460},{\"identifier\":{\"key\":\"ROOT\",\"namespace\":\"\"},\"name\":\"Open MCT\",\"type\":\"root\",\"composition\":[{\"key\":\"mine\",\"namespace\":\"\"}]}],\"navigationPath\":\"/browse/mine\",\"domainObject\":{\"identifier\":{\"key\":\"mine\",\"namespace\":\"\"},\"name\":\"My Items\",\"type\":\"folder\",\"composition\":[{\"key\":\"e78ca721-fb5e-409b-bf6d-597c87cb716f\",\"namespace\":\"\"},{\"key\":\"c6100044-56be-44b3-acca-6b9fddfb3849\",\"namespace\":\"\"}],\"location\":\"ROOT\",\"modified\":1732413602460,\"created\":1732413600960,\"persisted\":1732413602460}}]" + "value": "{\"mine\":{\"identifier\":{\"key\":\"mine\",\"namespace\":\"\"},\"name\":\"My Items\",\"type\":\"folder\",\"composition\":[{\"key\":\"d3014736-1182-4b70-8122-6d0c6ef540e1\",\"namespace\":\"\"},{\"key\":\"8c53d61f-b514-4535-be87-0fb20eb56576\",\"namespace\":\"\"}],\"location\":\"ROOT\",\"modified\":1732413605677,\"created\":1732413603298,\"persisted\":1732413605677},\"d3014736-1182-4b70-8122-6d0c6ef540e1\":{\"identifier\":{\"key\":\"d3014736-1182-4b70-8122-6d0c6ef540e1\",\"namespace\":\"\"},\"name\":\"Overlay Plot with Telemetry Object\",\"type\":\"telemetry.plot.overlay\",\"composition\":[{\"key\":\"8c53d61f-b514-4535-be87-0fb20eb56576\",\"namespace\":\"\"}],\"configuration\":{\"series\":[]},\"notes\":\"framework/generateLocalStorageData.e2e.spec.js\\nGenerate Visual Test Data @localStorage @generatedata @clock\\nGenerate Overlay Plot with Telemetry Object\\nchrome\",\"modified\":1732413607031,\"location\":\"mine\",\"created\":1732413605018,\"persisted\":1732413607031},\"8c53d61f-b514-4535-be87-0fb20eb56576\":{\"name\":\"VIPER Rover Heading\",\"type\":\"generator\",\"identifier\":{\"key\":\"8c53d61f-b514-4535-be87-0fb20eb56576\",\"namespace\":\"\"},\"telemetry\":{\"period\":10,\"amplitude\":1,\"offset\":0,\"dataRateInHz\":1,\"phase\":0,\"randomness\":0,\"loadDelay\":0,\"infinityValues\":false,\"exceedFloat32\":false,\"staleness\":false},\"modified\":1732413605677,\"location\":\"mine\",\"created\":1732413605677,\"persisted\":1732413605677}}" }, { "name": "mct-tree-expanded", "value": "[]" + }, + { + "name": "mct-recent-objects", + "value": "[{\"objectPath\":[{\"identifier\":{\"key\":\"8c53d61f-b514-4535-be87-0fb20eb56576\",\"namespace\":\"\"},\"name\":\"VIPER Rover Heading\",\"type\":\"generator\",\"telemetry\":{\"period\":10,\"amplitude\":1,\"offset\":0,\"dataRateInHz\":1,\"phase\":0,\"randomness\":0,\"loadDelay\":0,\"infinityValues\":false,\"exceedFloat32\":false,\"staleness\":false},\"modified\":1732413605677,\"location\":\"mine\",\"created\":1732413605677,\"persisted\":1732413605677},{\"identifier\":{\"key\":\"mine\",\"namespace\":\"\"},\"name\":\"My Items\",\"type\":\"folder\",\"composition\":[{\"key\":\"d3014736-1182-4b70-8122-6d0c6ef540e1\",\"namespace\":\"\"},{\"key\":\"8c53d61f-b514-4535-be87-0fb20eb56576\",\"namespace\":\"\"}],\"location\":\"ROOT\",\"modified\":1732413605677,\"created\":1732413603298,\"persisted\":1732413605677},{\"identifier\":{\"key\":\"ROOT\",\"namespace\":\"\"},\"name\":\"Open MCT\",\"type\":\"root\",\"composition\":[{\"key\":\"mine\",\"namespace\":\"\"}]}],\"navigationPath\":\"/browse/mine/8c53d61f-b514-4535-be87-0fb20eb56576\",\"domainObject\":{\"identifier\":{\"key\":\"8c53d61f-b514-4535-be87-0fb20eb56576\",\"namespace\":\"\"},\"name\":\"VIPER Rover Heading\",\"type\":\"generator\",\"telemetry\":{\"period\":10,\"amplitude\":1,\"offset\":0,\"dataRateInHz\":1,\"phase\":0,\"randomness\":0,\"loadDelay\":0,\"infinityValues\":false,\"exceedFloat32\":false,\"staleness\":false},\"modified\":1732413605677,\"location\":\"mine\",\"created\":1732413605677,\"persisted\":1732413605677}},{\"objectPath\":[{\"identifier\":{\"key\":\"d3014736-1182-4b70-8122-6d0c6ef540e1\",\"namespace\":\"\"},\"name\":\"Overlay Plot with Telemetry Object\",\"type\":\"telemetry.plot.overlay\",\"composition\":[{\"key\":\"8c53d61f-b514-4535-be87-0fb20eb56576\",\"namespace\":\"\"}],\"configuration\":{\"series\":[{\"identifier\":{\"key\":\"8c53d61f-b514-4535-be87-0fb20eb56576\",\"namespace\":\"\"}}]},\"notes\":\"framework/generateLocalStorageData.e2e.spec.js\\nGenerate Visual Test Data @localStorage @generatedata @clock\\nGenerate Overlay Plot with Telemetry Object\\nchrome\",\"modified\":1732413607031,\"location\":\"mine\",\"created\":1732413605018,\"persisted\":1732413607031},{\"identifier\":{\"key\":\"mine\",\"namespace\":\"\"},\"name\":\"My Items\",\"type\":\"folder\",\"composition\":[{\"key\":\"d3014736-1182-4b70-8122-6d0c6ef540e1\",\"namespace\":\"\"},{\"key\":\"8c53d61f-b514-4535-be87-0fb20eb56576\",\"namespace\":\"\"}],\"location\":\"ROOT\",\"modified\":1732413605677,\"created\":1732413603298,\"persisted\":1732413605677},{\"identifier\":{\"key\":\"ROOT\",\"namespace\":\"\"},\"name\":\"Open MCT\",\"type\":\"root\",\"composition\":[{\"key\":\"mine\",\"namespace\":\"\"}]}],\"navigationPath\":\"/browse/mine/d3014736-1182-4b70-8122-6d0c6ef540e1\",\"domainObject\":{\"identifier\":{\"key\":\"d3014736-1182-4b70-8122-6d0c6ef540e1\",\"namespace\":\"\"},\"name\":\"Overlay Plot with Telemetry Object\",\"type\":\"telemetry.plot.overlay\",\"composition\":[{\"key\":\"8c53d61f-b514-4535-be87-0fb20eb56576\",\"namespace\":\"\"}],\"configuration\":{\"series\":[{\"identifier\":{\"key\":\"8c53d61f-b514-4535-be87-0fb20eb56576\",\"namespace\":\"\"}}]},\"notes\":\"framework/generateLocalStorageData.e2e.spec.js\\nGenerate Visual Test Data @localStorage @generatedata @clock\\nGenerate Overlay Plot with Telemetry Object\\nchrome\",\"modified\":1732413607031,\"location\":\"mine\",\"created\":1732413605018,\"persisted\":1732413607031}},{\"objectPath\":[{\"identifier\":{\"key\":\"mine\",\"namespace\":\"\"},\"name\":\"My Items\",\"type\":\"folder\",\"composition\":[{\"key\":\"d3014736-1182-4b70-8122-6d0c6ef540e1\",\"namespace\":\"\"},{\"key\":\"8c53d61f-b514-4535-be87-0fb20eb56576\",\"namespace\":\"\"}],\"location\":\"ROOT\",\"modified\":1732413605677,\"created\":1732413603298,\"persisted\":1732413605677},{\"identifier\":{\"key\":\"ROOT\",\"namespace\":\"\"},\"name\":\"Open MCT\",\"type\":\"root\",\"composition\":[{\"key\":\"mine\",\"namespace\":\"\"}]}],\"navigationPath\":\"/browse/mine\",\"domainObject\":{\"identifier\":{\"key\":\"mine\",\"namespace\":\"\"},\"name\":\"My Items\",\"type\":\"folder\",\"composition\":[{\"key\":\"d3014736-1182-4b70-8122-6d0c6ef540e1\",\"namespace\":\"\"},{\"key\":\"8c53d61f-b514-4535-be87-0fb20eb56576\",\"namespace\":\"\"}],\"location\":\"ROOT\",\"modified\":1732413605677,\"created\":1732413603298,\"persisted\":1732413605677}}]" } ] } diff --git a/e2e/test-data/overlay_plot_with_delay_storage.json b/e2e/test-data/overlay_plot_with_delay_storage.json index ac3aeb65f0..4f13a19cad 100644 --- a/e2e/test-data/overlay_plot_with_delay_storage.json +++ b/e2e/test-data/overlay_plot_with_delay_storage.json @@ -6,7 +6,7 @@ "localStorage": [ { "name": "mct", - "value": "{\"mine\":{\"identifier\":{\"key\":\"mine\",\"namespace\":\"\"},\"name\":\"My Items\",\"type\":\"folder\",\"composition\":[{\"key\":\"67ca2e0a-b37e-4eda-86a4-ccdbb228bbc0\",\"namespace\":\"\"}],\"location\":\"ROOT\",\"modified\":1732413601720,\"created\":1732413600920,\"persisted\":1732413601720},\"67ca2e0a-b37e-4eda-86a4-ccdbb228bbc0\":{\"identifier\":{\"key\":\"67ca2e0a-b37e-4eda-86a4-ccdbb228bbc0\",\"namespace\":\"\"},\"name\":\"Overlay Plot with 5s Delay\",\"type\":\"telemetry.plot.overlay\",\"composition\":[{\"key\":\"8f524b49-ad06-47f9-98e0-087b31a2f3e0\",\"namespace\":\"\"}],\"configuration\":{\"series\":[{\"identifier\":{\"key\":\"8f524b49-ad06-47f9-98e0-087b31a2f3e0\",\"namespace\":\"\"}}]},\"notes\":\"framework/generateLocalStorageData.e2e.spec.js\\nGenerate Visual Test Data @localStorage @generatedata\\nGenerate Overlay Plot with 5s Delay\\nchrome\",\"modified\":1732413603020,\"location\":\"mine\",\"created\":1732413601720,\"persisted\":1732413603020},\"8f524b49-ad06-47f9-98e0-087b31a2f3e0\":{\"identifier\":{\"key\":\"8f524b49-ad06-47f9-98e0-087b31a2f3e0\",\"namespace\":\"\"},\"name\":\"VIPER Rover Heading\",\"type\":\"generator\",\"telemetry\":{\"period\":10,\"amplitude\":1,\"offset\":0,\"dataRateInHz\":1,\"phase\":0,\"randomness\":0,\"loadDelay\":5000,\"infinityValues\":false,\"exceedFloat32\":false,\"staleness\":false},\"modified\":1732413602920,\"location\":\"67ca2e0a-b37e-4eda-86a4-ccdbb228bbc0\",\"created\":1732413602420,\"persisted\":1732413602920}}" + "value": "{\"mine\":{\"identifier\":{\"key\":\"mine\",\"namespace\":\"\"},\"name\":\"My Items\",\"type\":\"folder\",\"composition\":[{\"key\":\"f34f457e-d7f4-4fc4-ba71-52e19e925646\",\"namespace\":\"\"}],\"location\":\"ROOT\",\"modified\":1732413605044,\"created\":1732413603140,\"persisted\":1732413605044},\"f34f457e-d7f4-4fc4-ba71-52e19e925646\":{\"identifier\":{\"key\":\"f34f457e-d7f4-4fc4-ba71-52e19e925646\",\"namespace\":\"\"},\"name\":\"Overlay Plot with 5s Delay\",\"type\":\"telemetry.plot.overlay\",\"composition\":[{\"key\":\"c568fa66-62e0-4eee-97eb-cdbc7421e556\",\"namespace\":\"\"}],\"configuration\":{\"series\":[{\"identifier\":{\"key\":\"c568fa66-62e0-4eee-97eb-cdbc7421e556\",\"namespace\":\"\"}}]},\"notes\":\"framework/generateLocalStorageData.e2e.spec.js\\nGenerate Visual Test Data @localStorage @generatedata @clock\\nGenerate Overlay Plot with 5s Delay\\nchrome\",\"modified\":1732413606208.9,\"location\":\"mine\",\"created\":1732413605044,\"persisted\":1732413606208.9},\"c568fa66-62e0-4eee-97eb-cdbc7421e556\":{\"identifier\":{\"key\":\"c568fa66-62e0-4eee-97eb-cdbc7421e556\",\"namespace\":\"\"},\"name\":\"VIPER Rover Heading\",\"type\":\"generator\",\"telemetry\":{\"period\":10,\"amplitude\":1,\"offset\":0,\"dataRateInHz\":1,\"phase\":0,\"randomness\":0,\"loadDelay\":5000,\"infinityValues\":false,\"exceedFloat32\":false,\"staleness\":false},\"modified\":1732413606049,\"location\":\"f34f457e-d7f4-4fc4-ba71-52e19e925646\",\"created\":1732413605554,\"persisted\":1732413606049}}" }, { "name": "mct-tree-expanded", diff --git a/e2e/test-data/recycled_local_storage.json b/e2e/test-data/recycled_local_storage.json index 7da5a89a39..2f9ee4db7e 100644 --- a/e2e/test-data/recycled_local_storage.json +++ b/e2e/test-data/recycled_local_storage.json @@ -6,7 +6,7 @@ "localStorage": [ { "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\":\"\"},{\"key\":\"72a5f66b-39a7-4f62-8c40-4a99a33d6a8e\",\"namespace\":\"\"},{\"key\":\"8e4d20f1-9a04-4de5-8db5-c7e08d27f70d\",\"namespace\":\"\"},{\"key\":\"3e294eae-6124-409b-a870-554d1bdcdd6f\",\"namespace\":\"\"},{\"key\":\"ec24d05d-5df5-4c96-9241-b73636cd19a9\",\"namespace\":\"\"},{\"key\":\"0ec517e8-6c11-4d98-89b5-c300fe61b304\",\"namespace\":\"\"},{\"key\":\"ffb49de1-af27-4318-a22f-59899988f4e9\",\"namespace\":\"\"},{\"key\":\"c6c65ad5-5c09-43c2-8a12-fdeb64d3e1a4\",\"namespace\":\"\"},{\"key\":\"62c4ade7-85ce-45bd-8cdb-25f0c58c8a28\",\"namespace\":\"\"}],\"location\":\"ROOT\",\"persisted\":1704955298732,\"modified\":1704955298732},\"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},\"72a5f66b-39a7-4f62-8c40-4a99a33d6a8e\":{\"name\":\"Unnamed Condition Set\",\"type\":\"conditionSet\",\"identifier\":{\"key\":\"72a5f66b-39a7-4f62-8c40-4a99a33d6a8e\",\"namespace\":\"\"},\"configuration\":{\"conditionTestData\":[],\"conditionCollection\":[{\"isDefault\":true,\"id\":\"2b6bf89f-877b-42b8-acc1-a9a575efdbe1\",\"configuration\":{\"name\":\"Default\",\"output\":\"Default\",\"trigger\":\"all\",\"criteria\":[]},\"summary\":\"Default condition\"}]},\"composition\":[],\"telemetry\":{},\"modified\":1658610682787,\"location\":\"mine\",\"persisted\":1658610682787},\"8e4d20f1-9a04-4de5-8db5-c7e08d27f70d\":{\"name\":\"Unnamed Condition Set\",\"type\":\"conditionSet\",\"identifier\":{\"key\":\"8e4d20f1-9a04-4de5-8db5-c7e08d27f70d\",\"namespace\":\"\"},\"configuration\":{\"conditionTestData\":[],\"conditionCollection\":[{\"isDefault\":true,\"id\":\"b9a9c413-4b94-401d-b0c7-5e404f182616\",\"configuration\":{\"name\":\"Default\",\"output\":\"Default\",\"trigger\":\"all\",\"criteria\":[]},\"summary\":\"Default condition\"}]},\"composition\":[],\"telemetry\":{},\"modified\":1658618261112,\"location\":\"mine\",\"persisted\":1658618261112},\"3e294eae-6124-409b-a870-554d1bdcdd6f\":{\"name\":\"Unnamed Condition Set\",\"type\":\"conditionSet\",\"identifier\":{\"key\":\"3e294eae-6124-409b-a870-554d1bdcdd6f\",\"namespace\":\"\"},\"configuration\":{\"conditionTestData\":[],\"conditionCollection\":[{\"isDefault\":true,\"id\":\"108043b1-9c88-4e1d-8deb-fbf2cdb528f9\",\"configuration\":{\"name\":\"Default\",\"output\":\"Default\",\"trigger\":\"all\",\"criteria\":[]},\"summary\":\"Default condition\"}]},\"composition\":[],\"telemetry\":{},\"modified\":1658618890910,\"location\":\"mine\",\"persisted\":1658618890910},\"ec24d05d-5df5-4c96-9241-b73636cd19a9\":{\"name\":\"Unnamed Condition Set\",\"type\":\"conditionSet\",\"identifier\":{\"key\":\"ec24d05d-5df5-4c96-9241-b73636cd19a9\",\"namespace\":\"\"},\"configuration\":{\"conditionTestData\":[],\"conditionCollection\":[{\"isDefault\":true,\"id\":\"4062bd9b-b788-43dd-ab0a-8fa10a78d4b3\",\"configuration\":{\"name\":\"Default\",\"output\":\"Default\",\"trigger\":\"all\",\"criteria\":[]},\"summary\":\"Default condition\"}]},\"composition\":[],\"telemetry\":{},\"modified\":1658619295363,\"location\":\"mine\",\"persisted\":1658619295363},\"0ec517e8-6c11-4d98-89b5-c300fe61b304\":{\"name\":\"Unnamed Condition Set\",\"type\":\"conditionSet\",\"identifier\":{\"key\":\"0ec517e8-6c11-4d98-89b5-c300fe61b304\",\"namespace\":\"\"},\"configuration\":{\"conditionTestData\":[],\"conditionCollection\":[{\"isDefault\":true,\"id\":\"2f1585da-6f7e-4ccd-8a20-590fdf177b5d\",\"configuration\":{\"name\":\"Default\",\"output\":\"Default\",\"trigger\":\"all\",\"criteria\":[]},\"summary\":\"Default condition\"}]},\"composition\":[],\"telemetry\":{},\"modified\":1689710689550,\"location\":\"mine\",\"created\":1689710689550,\"persisted\":1689710689550},\"ffb49de1-af27-4318-a22f-59899988f4e9\":{\"name\":\"Unnamed Condition Set\",\"type\":\"conditionSet\",\"identifier\":{\"key\":\"ffb49de1-af27-4318-a22f-59899988f4e9\",\"namespace\":\"\"},\"configuration\":{\"conditionTestData\":[],\"conditionCollection\":[{\"isDefault\":true,\"id\":\"0a04f110-e5c4-4503-9276-6e8f783d5bd5\",\"configuration\":{\"name\":\"Default\",\"output\":\"Default\",\"trigger\":\"all\",\"criteria\":[]},\"summary\":\"Default condition\"}]},\"composition\":[],\"telemetry\":{},\"modified\":1694110416938,\"location\":\"mine\",\"created\":1694110416938,\"persisted\":1694110416938},\"c6c65ad5-5c09-43c2-8a12-fdeb64d3e1a4\":{\"name\":\"Unnamed Condition Set\",\"type\":\"conditionSet\",\"identifier\":{\"key\":\"c6c65ad5-5c09-43c2-8a12-fdeb64d3e1a4\",\"namespace\":\"\"},\"configuration\":{\"conditionTestData\":[],\"conditionCollection\":[{\"isDefault\":true,\"id\":\"6c8bdbec-41f5-4138-a88f-ae6ebdbc9a90\",\"configuration\":{\"name\":\"Default\",\"output\":\"Default\",\"trigger\":\"all\",\"criteria\":[]},\"summary\":\"Default condition\"}]},\"composition\":[],\"telemetry\":{},\"modified\":1694112912985,\"location\":\"mine\",\"created\":1694112912985,\"persisted\":1694112912985},\"62c4ade7-85ce-45bd-8cdb-25f0c58c8a28\":{\"name\":\"Unnamed Condition Set\",\"type\":\"conditionSet\",\"identifier\":{\"key\":\"62c4ade7-85ce-45bd-8cdb-25f0c58c8a28\",\"namespace\":\"\"},\"configuration\":{\"conditionTestData\":[],\"conditionCollection\":[{\"isDefault\":true,\"id\":\"6776a2ec-fa49-4b06-80ed-a6eaf4f86f56\",\"configuration\":{\"name\":\"Default\",\"output\":\"Default\",\"trigger\":\"all\",\"criteria\":[]},\"summary\":\"Default condition\"}]},\"composition\":[],\"telemetry\":{},\"modified\":1704955298729,\"location\":\"mine\",\"created\":1704955298729,\"persisted\":1704955298729}}" + "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\":\"\"},{\"key\":\"72a5f66b-39a7-4f62-8c40-4a99a33d6a8e\",\"namespace\":\"\"},{\"key\":\"8e4d20f1-9a04-4de5-8db5-c7e08d27f70d\",\"namespace\":\"\"},{\"key\":\"3e294eae-6124-409b-a870-554d1bdcdd6f\",\"namespace\":\"\"},{\"key\":\"ec24d05d-5df5-4c96-9241-b73636cd19a9\",\"namespace\":\"\"},{\"key\":\"0ec517e8-6c11-4d98-89b5-c300fe61b304\",\"namespace\":\"\"},{\"key\":\"ffb49de1-af27-4318-a22f-59899988f4e9\",\"namespace\":\"\"},{\"key\":\"c6c65ad5-5c09-43c2-8a12-fdeb64d3e1a4\",\"namespace\":\"\"},{\"key\":\"62c4ade7-85ce-45bd-8cdb-25f0c58c8a28\",\"namespace\":\"\"},{\"key\":\"c1717964-ffed-47aa-9ed9-647ba5a3db67\",\"namespace\":\"\"},{\"key\":\"10581641-5de3-4606-95aa-04cd811f2f53\",\"namespace\":\"\"},{\"key\":\"d9d79500-916d-4ff2-a7ea-6cf300c85ce3\",\"namespace\":\"\"}],\"location\":\"ROOT\",\"persisted\":1721850933441,\"modified\":1721850933441},\"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},\"72a5f66b-39a7-4f62-8c40-4a99a33d6a8e\":{\"name\":\"Unnamed Condition Set\",\"type\":\"conditionSet\",\"identifier\":{\"key\":\"72a5f66b-39a7-4f62-8c40-4a99a33d6a8e\",\"namespace\":\"\"},\"configuration\":{\"conditionTestData\":[],\"conditionCollection\":[{\"isDefault\":true,\"id\":\"2b6bf89f-877b-42b8-acc1-a9a575efdbe1\",\"configuration\":{\"name\":\"Default\",\"output\":\"Default\",\"trigger\":\"all\",\"criteria\":[]},\"summary\":\"Default condition\"}]},\"composition\":[],\"telemetry\":{},\"modified\":1658610682787,\"location\":\"mine\",\"persisted\":1658610682787},\"8e4d20f1-9a04-4de5-8db5-c7e08d27f70d\":{\"name\":\"Unnamed Condition Set\",\"type\":\"conditionSet\",\"identifier\":{\"key\":\"8e4d20f1-9a04-4de5-8db5-c7e08d27f70d\",\"namespace\":\"\"},\"configuration\":{\"conditionTestData\":[],\"conditionCollection\":[{\"isDefault\":true,\"id\":\"b9a9c413-4b94-401d-b0c7-5e404f182616\",\"configuration\":{\"name\":\"Default\",\"output\":\"Default\",\"trigger\":\"all\",\"criteria\":[]},\"summary\":\"Default condition\"}]},\"composition\":[],\"telemetry\":{},\"modified\":1658618261112,\"location\":\"mine\",\"persisted\":1658618261112},\"3e294eae-6124-409b-a870-554d1bdcdd6f\":{\"name\":\"Unnamed Condition Set\",\"type\":\"conditionSet\",\"identifier\":{\"key\":\"3e294eae-6124-409b-a870-554d1bdcdd6f\",\"namespace\":\"\"},\"configuration\":{\"conditionTestData\":[],\"conditionCollection\":[{\"isDefault\":true,\"id\":\"108043b1-9c88-4e1d-8deb-fbf2cdb528f9\",\"configuration\":{\"name\":\"Default\",\"output\":\"Default\",\"trigger\":\"all\",\"criteria\":[]},\"summary\":\"Default condition\"}]},\"composition\":[],\"telemetry\":{},\"modified\":1658618890910,\"location\":\"mine\",\"persisted\":1658618890910},\"ec24d05d-5df5-4c96-9241-b73636cd19a9\":{\"name\":\"Unnamed Condition Set\",\"type\":\"conditionSet\",\"identifier\":{\"key\":\"ec24d05d-5df5-4c96-9241-b73636cd19a9\",\"namespace\":\"\"},\"configuration\":{\"conditionTestData\":[],\"conditionCollection\":[{\"isDefault\":true,\"id\":\"4062bd9b-b788-43dd-ab0a-8fa10a78d4b3\",\"configuration\":{\"name\":\"Default\",\"output\":\"Default\",\"trigger\":\"all\",\"criteria\":[]},\"summary\":\"Default condition\"}]},\"composition\":[],\"telemetry\":{},\"modified\":1658619295363,\"location\":\"mine\",\"persisted\":1658619295363},\"0ec517e8-6c11-4d98-89b5-c300fe61b304\":{\"name\":\"Unnamed Condition Set\",\"type\":\"conditionSet\",\"identifier\":{\"key\":\"0ec517e8-6c11-4d98-89b5-c300fe61b304\",\"namespace\":\"\"},\"configuration\":{\"conditionTestData\":[],\"conditionCollection\":[{\"isDefault\":true,\"id\":\"2f1585da-6f7e-4ccd-8a20-590fdf177b5d\",\"configuration\":{\"name\":\"Default\",\"output\":\"Default\",\"trigger\":\"all\",\"criteria\":[]},\"summary\":\"Default condition\"}]},\"composition\":[],\"telemetry\":{},\"modified\":1689710689550,\"location\":\"mine\",\"created\":1689710689550,\"persisted\":1689710689550},\"ffb49de1-af27-4318-a22f-59899988f4e9\":{\"name\":\"Unnamed Condition Set\",\"type\":\"conditionSet\",\"identifier\":{\"key\":\"ffb49de1-af27-4318-a22f-59899988f4e9\",\"namespace\":\"\"},\"configuration\":{\"conditionTestData\":[],\"conditionCollection\":[{\"isDefault\":true,\"id\":\"0a04f110-e5c4-4503-9276-6e8f783d5bd5\",\"configuration\":{\"name\":\"Default\",\"output\":\"Default\",\"trigger\":\"all\",\"criteria\":[]},\"summary\":\"Default condition\"}]},\"composition\":[],\"telemetry\":{},\"modified\":1694110416938,\"location\":\"mine\",\"created\":1694110416938,\"persisted\":1694110416938},\"c6c65ad5-5c09-43c2-8a12-fdeb64d3e1a4\":{\"name\":\"Unnamed Condition Set\",\"type\":\"conditionSet\",\"identifier\":{\"key\":\"c6c65ad5-5c09-43c2-8a12-fdeb64d3e1a4\",\"namespace\":\"\"},\"configuration\":{\"conditionTestData\":[],\"conditionCollection\":[{\"isDefault\":true,\"id\":\"6c8bdbec-41f5-4138-a88f-ae6ebdbc9a90\",\"configuration\":{\"name\":\"Default\",\"output\":\"Default\",\"trigger\":\"all\",\"criteria\":[]},\"summary\":\"Default condition\"}]},\"composition\":[],\"telemetry\":{},\"modified\":1694112912985,\"location\":\"mine\",\"created\":1694112912985,\"persisted\":1694112912985},\"62c4ade7-85ce-45bd-8cdb-25f0c58c8a28\":{\"name\":\"Unnamed Condition Set\",\"type\":\"conditionSet\",\"identifier\":{\"key\":\"62c4ade7-85ce-45bd-8cdb-25f0c58c8a28\",\"namespace\":\"\"},\"configuration\":{\"conditionTestData\":[],\"conditionCollection\":[{\"isDefault\":true,\"id\":\"6776a2ec-fa49-4b06-80ed-a6eaf4f86f56\",\"configuration\":{\"name\":\"Default\",\"output\":\"Default\",\"trigger\":\"all\",\"criteria\":[]},\"summary\":\"Default condition\"}]},\"composition\":[],\"telemetry\":{},\"modified\":1704955298729,\"location\":\"mine\",\"created\":1704955298729,\"persisted\":1704955298729},\"c1717964-ffed-47aa-9ed9-647ba5a3db67\":{\"name\":\"Unnamed Condition Set\",\"type\":\"conditionSet\",\"identifier\":{\"key\":\"c1717964-ffed-47aa-9ed9-647ba5a3db67\",\"namespace\":\"\"},\"configuration\":{\"conditionTestData\":[],\"conditionCollection\":[{\"isDefault\":true,\"id\":\"9a72ce62-012c-4a81-8f2c-51db5410de76\",\"configuration\":{\"name\":\"Default\",\"output\":\"Default\",\"trigger\":\"all\",\"criteria\":[]},\"summary\":\"Default condition\"}]},\"composition\":[],\"telemetry\":{},\"modified\":1721850626897,\"location\":\"mine\",\"created\":1721850626897,\"persisted\":1721850626897},\"10581641-5de3-4606-95aa-04cd811f2f53\":{\"name\":\"Unnamed Condition Set\",\"type\":\"conditionSet\",\"identifier\":{\"key\":\"10581641-5de3-4606-95aa-04cd811f2f53\",\"namespace\":\"\"},\"configuration\":{\"conditionTestData\":[],\"conditionCollection\":[{\"isDefault\":true,\"id\":\"71e8050e-e063-4a35-b891-f38ddf63fa6d\",\"configuration\":{\"name\":\"Default\",\"output\":\"Default\",\"trigger\":\"all\",\"criteria\":[]},\"summary\":\"Default condition\"}]},\"composition\":[],\"telemetry\":{},\"modified\":1721850725791,\"location\":\"mine\",\"created\":1721850725791,\"persisted\":1721850725791},\"d9d79500-916d-4ff2-a7ea-6cf300c85ce3\":{\"name\":\"Unnamed Condition Set\",\"type\":\"conditionSet\",\"identifier\":{\"key\":\"d9d79500-916d-4ff2-a7ea-6cf300c85ce3\",\"namespace\":\"\"},\"configuration\":{\"conditionTestData\":[],\"conditionCollection\":[{\"isDefault\":true,\"id\":\"91ebe1a1-dcac-49b0-9985-d3e32cef5260\",\"configuration\":{\"name\":\"Default\",\"output\":\"Default\",\"trigger\":\"all\",\"criteria\":[]},\"summary\":\"Default condition\"}]},\"composition\":[],\"telemetry\":{},\"modified\":1721850933438,\"location\":\"mine\",\"created\":1721850933438,\"persisted\":1721850933439}}" }, { "name": "mct-tree-expanded", @@ -18,7 +18,7 @@ }, { "name": "mct-recent-objects", - "value": "[{\"objectPath\":[{\"identifier\":{\"key\":\"62c4ade7-85ce-45bd-8cdb-25f0c58c8a28\",\"namespace\":\"\"},\"name\":\"Unnamed Condition Set\",\"type\":\"conditionSet\",\"configuration\":{\"conditionTestData\":[],\"conditionCollection\":[{\"isDefault\":true,\"id\":\"6776a2ec-fa49-4b06-80ed-a6eaf4f86f56\",\"configuration\":{\"name\":\"Default\",\"output\":\"Default\",\"trigger\":\"all\",\"criteria\":[]},\"summary\":\"Default condition\"}]},\"composition\":[],\"telemetry\":{},\"modified\":1704955298729,\"location\":\"mine\",\"created\":1704955298729,\"persisted\":1704955298729},{\"identifier\":{\"key\":\"mine\",\"namespace\":\"\"},\"name\":\"My Items\",\"type\":\"folder\",\"composition\":[{\"key\":\"f64bea3b-58a7-4586-8c05-8b651e5f0bfd\",\"namespace\":\"\"},{\"key\":\"2d02a680-eb7e-4645-bba2-dd298f76efb8\",\"namespace\":\"\"},{\"key\":\"72a5f66b-39a7-4f62-8c40-4a99a33d6a8e\",\"namespace\":\"\"},{\"key\":\"8e4d20f1-9a04-4de5-8db5-c7e08d27f70d\",\"namespace\":\"\"},{\"key\":\"3e294eae-6124-409b-a870-554d1bdcdd6f\",\"namespace\":\"\"},{\"key\":\"ec24d05d-5df5-4c96-9241-b73636cd19a9\",\"namespace\":\"\"},{\"key\":\"0ec517e8-6c11-4d98-89b5-c300fe61b304\",\"namespace\":\"\"},{\"key\":\"ffb49de1-af27-4318-a22f-59899988f4e9\",\"namespace\":\"\"},{\"key\":\"c6c65ad5-5c09-43c2-8a12-fdeb64d3e1a4\",\"namespace\":\"\"},{\"key\":\"62c4ade7-85ce-45bd-8cdb-25f0c58c8a28\",\"namespace\":\"\"}],\"location\":\"ROOT\",\"persisted\":1704955298732,\"modified\":1704955298732},{\"identifier\":{\"key\":\"ROOT\",\"namespace\":\"\"},\"name\":\"Open MCT\",\"type\":\"root\",\"composition\":[{\"key\":\"mine\",\"namespace\":\"\"}]}],\"navigationPath\":\"/browse/mine/62c4ade7-85ce-45bd-8cdb-25f0c58c8a28\",\"domainObject\":{\"identifier\":{\"key\":\"62c4ade7-85ce-45bd-8cdb-25f0c58c8a28\",\"namespace\":\"\"},\"name\":\"Unnamed Condition Set\",\"type\":\"conditionSet\",\"configuration\":{\"conditionTestData\":[],\"conditionCollection\":[{\"isDefault\":true,\"id\":\"6776a2ec-fa49-4b06-80ed-a6eaf4f86f56\",\"configuration\":{\"name\":\"Default\",\"output\":\"Default\",\"trigger\":\"all\",\"criteria\":[]},\"summary\":\"Default condition\"}]},\"composition\":[],\"telemetry\":{},\"modified\":1704955298729,\"location\":\"mine\",\"created\":1704955298729,\"persisted\":1704955298729}},{\"objectPath\":[{\"identifier\":{\"key\":\"mine\",\"namespace\":\"\"},\"name\":\"My Items\",\"type\":\"folder\",\"composition\":[{\"key\":\"f64bea3b-58a7-4586-8c05-8b651e5f0bfd\",\"namespace\":\"\"},{\"key\":\"2d02a680-eb7e-4645-bba2-dd298f76efb8\",\"namespace\":\"\"},{\"key\":\"72a5f66b-39a7-4f62-8c40-4a99a33d6a8e\",\"namespace\":\"\"},{\"key\":\"8e4d20f1-9a04-4de5-8db5-c7e08d27f70d\",\"namespace\":\"\"},{\"key\":\"3e294eae-6124-409b-a870-554d1bdcdd6f\",\"namespace\":\"\"},{\"key\":\"ec24d05d-5df5-4c96-9241-b73636cd19a9\",\"namespace\":\"\"},{\"key\":\"0ec517e8-6c11-4d98-89b5-c300fe61b304\",\"namespace\":\"\"},{\"key\":\"ffb49de1-af27-4318-a22f-59899988f4e9\",\"namespace\":\"\"},{\"key\":\"c6c65ad5-5c09-43c2-8a12-fdeb64d3e1a4\",\"namespace\":\"\"},{\"key\":\"62c4ade7-85ce-45bd-8cdb-25f0c58c8a28\",\"namespace\":\"\"}],\"location\":\"ROOT\",\"persisted\":1704955298732,\"modified\":1704955298732},{\"identifier\":{\"key\":\"ROOT\",\"namespace\":\"\"},\"name\":\"Open MCT\",\"type\":\"root\",\"composition\":[{\"key\":\"mine\",\"namespace\":\"\"}]}],\"navigationPath\":\"/browse/mine\",\"domainObject\":{\"identifier\":{\"key\":\"mine\",\"namespace\":\"\"},\"name\":\"My Items\",\"type\":\"folder\",\"composition\":[{\"key\":\"f64bea3b-58a7-4586-8c05-8b651e5f0bfd\",\"namespace\":\"\"},{\"key\":\"2d02a680-eb7e-4645-bba2-dd298f76efb8\",\"namespace\":\"\"},{\"key\":\"72a5f66b-39a7-4f62-8c40-4a99a33d6a8e\",\"namespace\":\"\"},{\"key\":\"8e4d20f1-9a04-4de5-8db5-c7e08d27f70d\",\"namespace\":\"\"},{\"key\":\"3e294eae-6124-409b-a870-554d1bdcdd6f\",\"namespace\":\"\"},{\"key\":\"ec24d05d-5df5-4c96-9241-b73636cd19a9\",\"namespace\":\"\"},{\"key\":\"0ec517e8-6c11-4d98-89b5-c300fe61b304\",\"namespace\":\"\"},{\"key\":\"ffb49de1-af27-4318-a22f-59899988f4e9\",\"namespace\":\"\"},{\"key\":\"c6c65ad5-5c09-43c2-8a12-fdeb64d3e1a4\",\"namespace\":\"\"},{\"key\":\"62c4ade7-85ce-45bd-8cdb-25f0c58c8a28\",\"namespace\":\"\"}],\"location\":\"ROOT\",\"persisted\":1704955298732,\"modified\":1704955298732}},{\"objectPath\":[{\"identifier\":{\"key\":\"c6c65ad5-5c09-43c2-8a12-fdeb64d3e1a4\",\"namespace\":\"\"},\"name\":\"Unnamed Condition Set\",\"type\":\"conditionSet\",\"configuration\":{\"conditionTestData\":[],\"conditionCollection\":[{\"isDefault\":true,\"id\":\"6c8bdbec-41f5-4138-a88f-ae6ebdbc9a90\",\"configuration\":{\"name\":\"Default\",\"output\":\"Default\",\"trigger\":\"all\",\"criteria\":[]},\"summary\":\"Default condition\"}]},\"composition\":[],\"telemetry\":{},\"modified\":1694112912985,\"location\":\"mine\",\"created\":1694112912985,\"persisted\":1694112912985},{\"identifier\":{\"key\":\"mine\",\"namespace\":\"\"},\"name\":\"My Items\",\"type\":\"folder\",\"composition\":[{\"key\":\"f64bea3b-58a7-4586-8c05-8b651e5f0bfd\",\"namespace\":\"\"},{\"key\":\"2d02a680-eb7e-4645-bba2-dd298f76efb8\",\"namespace\":\"\"},{\"key\":\"72a5f66b-39a7-4f62-8c40-4a99a33d6a8e\",\"namespace\":\"\"},{\"key\":\"8e4d20f1-9a04-4de5-8db5-c7e08d27f70d\",\"namespace\":\"\"},{\"key\":\"3e294eae-6124-409b-a870-554d1bdcdd6f\",\"namespace\":\"\"},{\"key\":\"ec24d05d-5df5-4c96-9241-b73636cd19a9\",\"namespace\":\"\"},{\"key\":\"0ec517e8-6c11-4d98-89b5-c300fe61b304\",\"namespace\":\"\"},{\"key\":\"ffb49de1-af27-4318-a22f-59899988f4e9\",\"namespace\":\"\"},{\"key\":\"c6c65ad5-5c09-43c2-8a12-fdeb64d3e1a4\",\"namespace\":\"\"},{\"key\":\"62c4ade7-85ce-45bd-8cdb-25f0c58c8a28\",\"namespace\":\"\"}],\"location\":\"ROOT\",\"persisted\":1704955298732,\"modified\":1704955298732},{\"identifier\":{\"key\":\"ROOT\",\"namespace\":\"\"},\"name\":\"Open MCT\",\"type\":\"root\",\"composition\":[{\"key\":\"mine\",\"namespace\":\"\"}]}],\"navigationPath\":\"/browse/mine/c6c65ad5-5c09-43c2-8a12-fdeb64d3e1a4\",\"domainObject\":{\"identifier\":{\"key\":\"c6c65ad5-5c09-43c2-8a12-fdeb64d3e1a4\",\"namespace\":\"\"},\"name\":\"Unnamed Condition Set\",\"type\":\"conditionSet\",\"configuration\":{\"conditionTestData\":[],\"conditionCollection\":[{\"isDefault\":true,\"id\":\"6c8bdbec-41f5-4138-a88f-ae6ebdbc9a90\",\"configuration\":{\"name\":\"Default\",\"output\":\"Default\",\"trigger\":\"all\",\"criteria\":[]},\"summary\":\"Default condition\"}]},\"composition\":[],\"telemetry\":{},\"modified\":1694112912985,\"location\":\"mine\",\"created\":1694112912985,\"persisted\":1694112912985}},{\"objectPath\":[{\"identifier\":{\"key\":\"ffb49de1-af27-4318-a22f-59899988f4e9\",\"namespace\":\"\"},\"name\":\"Unnamed Condition Set\",\"type\":\"conditionSet\",\"configuration\":{\"conditionTestData\":[],\"conditionCollection\":[{\"isDefault\":true,\"id\":\"0a04f110-e5c4-4503-9276-6e8f783d5bd5\",\"configuration\":{\"name\":\"Default\",\"output\":\"Default\",\"trigger\":\"all\",\"criteria\":[]},\"summary\":\"Default condition\"}]},\"composition\":[],\"telemetry\":{},\"modified\":1694110416938,\"location\":\"mine\",\"created\":1694110416938,\"persisted\":1694110416938},{\"identifier\":{\"key\":\"mine\",\"namespace\":\"\"},\"name\":\"My Items\",\"type\":\"folder\",\"composition\":[{\"key\":\"f64bea3b-58a7-4586-8c05-8b651e5f0bfd\",\"namespace\":\"\"},{\"key\":\"2d02a680-eb7e-4645-bba2-dd298f76efb8\",\"namespace\":\"\"},{\"key\":\"72a5f66b-39a7-4f62-8c40-4a99a33d6a8e\",\"namespace\":\"\"},{\"key\":\"8e4d20f1-9a04-4de5-8db5-c7e08d27f70d\",\"namespace\":\"\"},{\"key\":\"3e294eae-6124-409b-a870-554d1bdcdd6f\",\"namespace\":\"\"},{\"key\":\"ec24d05d-5df5-4c96-9241-b73636cd19a9\",\"namespace\":\"\"},{\"key\":\"0ec517e8-6c11-4d98-89b5-c300fe61b304\",\"namespace\":\"\"},{\"key\":\"ffb49de1-af27-4318-a22f-59899988f4e9\",\"namespace\":\"\"},{\"key\":\"c6c65ad5-5c09-43c2-8a12-fdeb64d3e1a4\",\"namespace\":\"\"},{\"key\":\"62c4ade7-85ce-45bd-8cdb-25f0c58c8a28\",\"namespace\":\"\"}],\"location\":\"ROOT\",\"persisted\":1704955298732,\"modified\":1704955298732},{\"identifier\":{\"key\":\"ROOT\",\"namespace\":\"\"},\"name\":\"Open MCT\",\"type\":\"root\",\"composition\":[{\"key\":\"mine\",\"namespace\":\"\"}]}],\"navigationPath\":\"/browse/mine/ffb49de1-af27-4318-a22f-59899988f4e9\",\"domainObject\":{\"identifier\":{\"key\":\"ffb49de1-af27-4318-a22f-59899988f4e9\",\"namespace\":\"\"},\"name\":\"Unnamed Condition Set\",\"type\":\"conditionSet\",\"configuration\":{\"conditionTestData\":[],\"conditionCollection\":[{\"isDefault\":true,\"id\":\"0a04f110-e5c4-4503-9276-6e8f783d5bd5\",\"configuration\":{\"name\":\"Default\",\"output\":\"Default\",\"trigger\":\"all\",\"criteria\":[]},\"summary\":\"Default condition\"}]},\"composition\":[],\"telemetry\":{},\"modified\":1694110416938,\"location\":\"mine\",\"created\":1694110416938,\"persisted\":1694110416938}},{\"objectPath\":[{\"identifier\":{\"key\":\"0ec517e8-6c11-4d98-89b5-c300fe61b304\",\"namespace\":\"\"},\"name\":\"Unnamed Condition Set\",\"type\":\"conditionSet\",\"configuration\":{\"conditionTestData\":[],\"conditionCollection\":[{\"isDefault\":true,\"id\":\"2f1585da-6f7e-4ccd-8a20-590fdf177b5d\",\"configuration\":{\"name\":\"Default\",\"output\":\"Default\",\"trigger\":\"all\",\"criteria\":[]},\"summary\":\"Default condition\"}]},\"composition\":[],\"telemetry\":{},\"modified\":1689710689550,\"location\":\"mine\",\"created\":1689710689550,\"persisted\":1689710689550},{\"identifier\":{\"key\":\"mine\",\"namespace\":\"\"},\"name\":\"My Items\",\"type\":\"folder\",\"composition\":[{\"key\":\"f64bea3b-58a7-4586-8c05-8b651e5f0bfd\",\"namespace\":\"\"},{\"key\":\"2d02a680-eb7e-4645-bba2-dd298f76efb8\",\"namespace\":\"\"},{\"key\":\"72a5f66b-39a7-4f62-8c40-4a99a33d6a8e\",\"namespace\":\"\"},{\"key\":\"8e4d20f1-9a04-4de5-8db5-c7e08d27f70d\",\"namespace\":\"\"},{\"key\":\"3e294eae-6124-409b-a870-554d1bdcdd6f\",\"namespace\":\"\"},{\"key\":\"ec24d05d-5df5-4c96-9241-b73636cd19a9\",\"namespace\":\"\"},{\"key\":\"0ec517e8-6c11-4d98-89b5-c300fe61b304\",\"namespace\":\"\"},{\"key\":\"ffb49de1-af27-4318-a22f-59899988f4e9\",\"namespace\":\"\"},{\"key\":\"c6c65ad5-5c09-43c2-8a12-fdeb64d3e1a4\",\"namespace\":\"\"},{\"key\":\"62c4ade7-85ce-45bd-8cdb-25f0c58c8a28\",\"namespace\":\"\"}],\"location\":\"ROOT\",\"persisted\":1704955298732,\"modified\":1704955298732},{\"identifier\":{\"key\":\"ROOT\",\"namespace\":\"\"},\"name\":\"Open MCT\",\"type\":\"root\",\"composition\":[{\"key\":\"mine\",\"namespace\":\"\"}]}],\"navigationPath\":\"/browse/mine/0ec517e8-6c11-4d98-89b5-c300fe61b304\",\"domainObject\":{\"identifier\":{\"key\":\"0ec517e8-6c11-4d98-89b5-c300fe61b304\",\"namespace\":\"\"},\"name\":\"Unnamed Condition Set\",\"type\":\"conditionSet\",\"configuration\":{\"conditionTestData\":[],\"conditionCollection\":[{\"isDefault\":true,\"id\":\"2f1585da-6f7e-4ccd-8a20-590fdf177b5d\",\"configuration\":{\"name\":\"Default\",\"output\":\"Default\",\"trigger\":\"all\",\"criteria\":[]},\"summary\":\"Default condition\"}]},\"composition\":[],\"telemetry\":{},\"modified\":1689710689550,\"location\":\"mine\",\"created\":1689710689550,\"persisted\":1689710689550}}]" + "value": "[{\"objectPath\":[{\"identifier\":{\"key\":\"d9d79500-916d-4ff2-a7ea-6cf300c85ce3\",\"namespace\":\"\"},\"name\":\"Unnamed Condition Set\",\"type\":\"conditionSet\",\"configuration\":{\"conditionTestData\":[],\"conditionCollection\":[{\"isDefault\":true,\"id\":\"91ebe1a1-dcac-49b0-9985-d3e32cef5260\",\"configuration\":{\"name\":\"Default\",\"output\":\"Default\",\"trigger\":\"all\",\"criteria\":[]},\"summary\":\"Default condition\"}]},\"composition\":[],\"telemetry\":{},\"modified\":1721850933438,\"location\":\"mine\",\"created\":1721850933438,\"persisted\":1721850933439},{\"identifier\":{\"key\":\"mine\",\"namespace\":\"\"},\"name\":\"My Items\",\"type\":\"folder\",\"composition\":[{\"key\":\"f64bea3b-58a7-4586-8c05-8b651e5f0bfd\",\"namespace\":\"\"},{\"key\":\"2d02a680-eb7e-4645-bba2-dd298f76efb8\",\"namespace\":\"\"},{\"key\":\"72a5f66b-39a7-4f62-8c40-4a99a33d6a8e\",\"namespace\":\"\"},{\"key\":\"8e4d20f1-9a04-4de5-8db5-c7e08d27f70d\",\"namespace\":\"\"},{\"key\":\"3e294eae-6124-409b-a870-554d1bdcdd6f\",\"namespace\":\"\"},{\"key\":\"ec24d05d-5df5-4c96-9241-b73636cd19a9\",\"namespace\":\"\"},{\"key\":\"0ec517e8-6c11-4d98-89b5-c300fe61b304\",\"namespace\":\"\"},{\"key\":\"ffb49de1-af27-4318-a22f-59899988f4e9\",\"namespace\":\"\"},{\"key\":\"c6c65ad5-5c09-43c2-8a12-fdeb64d3e1a4\",\"namespace\":\"\"},{\"key\":\"62c4ade7-85ce-45bd-8cdb-25f0c58c8a28\",\"namespace\":\"\"},{\"key\":\"c1717964-ffed-47aa-9ed9-647ba5a3db67\",\"namespace\":\"\"},{\"key\":\"10581641-5de3-4606-95aa-04cd811f2f53\",\"namespace\":\"\"},{\"key\":\"d9d79500-916d-4ff2-a7ea-6cf300c85ce3\",\"namespace\":\"\"}],\"location\":\"ROOT\",\"persisted\":1721850933441,\"modified\":1721850933441},{\"identifier\":{\"key\":\"ROOT\",\"namespace\":\"\"},\"name\":\"Open MCT\",\"type\":\"root\",\"composition\":[{\"key\":\"mine\",\"namespace\":\"\"}]}],\"navigationPath\":\"/browse/mine/d9d79500-916d-4ff2-a7ea-6cf300c85ce3\",\"domainObject\":{\"identifier\":{\"key\":\"d9d79500-916d-4ff2-a7ea-6cf300c85ce3\",\"namespace\":\"\"},\"name\":\"Unnamed Condition Set\",\"type\":\"conditionSet\",\"configuration\":{\"conditionTestData\":[],\"conditionCollection\":[{\"isDefault\":true,\"id\":\"91ebe1a1-dcac-49b0-9985-d3e32cef5260\",\"configuration\":{\"name\":\"Default\",\"output\":\"Default\",\"trigger\":\"all\",\"criteria\":[]},\"summary\":\"Default condition\"}]},\"composition\":[],\"telemetry\":{},\"modified\":1721850933438,\"location\":\"mine\",\"created\":1721850933438,\"persisted\":1721850933439}},{\"objectPath\":[{\"identifier\":{\"key\":\"mine\",\"namespace\":\"\"},\"name\":\"My Items\",\"type\":\"folder\",\"composition\":[{\"key\":\"f64bea3b-58a7-4586-8c05-8b651e5f0bfd\",\"namespace\":\"\"},{\"key\":\"2d02a680-eb7e-4645-bba2-dd298f76efb8\",\"namespace\":\"\"},{\"key\":\"72a5f66b-39a7-4f62-8c40-4a99a33d6a8e\",\"namespace\":\"\"},{\"key\":\"8e4d20f1-9a04-4de5-8db5-c7e08d27f70d\",\"namespace\":\"\"},{\"key\":\"3e294eae-6124-409b-a870-554d1bdcdd6f\",\"namespace\":\"\"},{\"key\":\"ec24d05d-5df5-4c96-9241-b73636cd19a9\",\"namespace\":\"\"},{\"key\":\"0ec517e8-6c11-4d98-89b5-c300fe61b304\",\"namespace\":\"\"},{\"key\":\"ffb49de1-af27-4318-a22f-59899988f4e9\",\"namespace\":\"\"},{\"key\":\"c6c65ad5-5c09-43c2-8a12-fdeb64d3e1a4\",\"namespace\":\"\"},{\"key\":\"62c4ade7-85ce-45bd-8cdb-25f0c58c8a28\",\"namespace\":\"\"},{\"key\":\"c1717964-ffed-47aa-9ed9-647ba5a3db67\",\"namespace\":\"\"},{\"key\":\"10581641-5de3-4606-95aa-04cd811f2f53\",\"namespace\":\"\"},{\"key\":\"d9d79500-916d-4ff2-a7ea-6cf300c85ce3\",\"namespace\":\"\"}],\"location\":\"ROOT\",\"persisted\":1721850933441,\"modified\":1721850933441},{\"identifier\":{\"key\":\"ROOT\",\"namespace\":\"\"},\"name\":\"Open MCT\",\"type\":\"root\",\"composition\":[{\"key\":\"mine\",\"namespace\":\"\"}]}],\"navigationPath\":\"/browse/mine\",\"domainObject\":{\"identifier\":{\"key\":\"mine\",\"namespace\":\"\"},\"name\":\"My Items\",\"type\":\"folder\",\"composition\":[{\"key\":\"f64bea3b-58a7-4586-8c05-8b651e5f0bfd\",\"namespace\":\"\"},{\"key\":\"2d02a680-eb7e-4645-bba2-dd298f76efb8\",\"namespace\":\"\"},{\"key\":\"72a5f66b-39a7-4f62-8c40-4a99a33d6a8e\",\"namespace\":\"\"},{\"key\":\"8e4d20f1-9a04-4de5-8db5-c7e08d27f70d\",\"namespace\":\"\"},{\"key\":\"3e294eae-6124-409b-a870-554d1bdcdd6f\",\"namespace\":\"\"},{\"key\":\"ec24d05d-5df5-4c96-9241-b73636cd19a9\",\"namespace\":\"\"},{\"key\":\"0ec517e8-6c11-4d98-89b5-c300fe61b304\",\"namespace\":\"\"},{\"key\":\"ffb49de1-af27-4318-a22f-59899988f4e9\",\"namespace\":\"\"},{\"key\":\"c6c65ad5-5c09-43c2-8a12-fdeb64d3e1a4\",\"namespace\":\"\"},{\"key\":\"62c4ade7-85ce-45bd-8cdb-25f0c58c8a28\",\"namespace\":\"\"},{\"key\":\"c1717964-ffed-47aa-9ed9-647ba5a3db67\",\"namespace\":\"\"},{\"key\":\"10581641-5de3-4606-95aa-04cd811f2f53\",\"namespace\":\"\"},{\"key\":\"d9d79500-916d-4ff2-a7ea-6cf300c85ce3\",\"namespace\":\"\"}],\"location\":\"ROOT\",\"persisted\":1721850933441,\"modified\":1721850933441}},{\"objectPath\":[{\"identifier\":{\"key\":\"10581641-5de3-4606-95aa-04cd811f2f53\",\"namespace\":\"\"},\"name\":\"Unnamed Condition Set\",\"type\":\"conditionSet\",\"configuration\":{\"conditionTestData\":[],\"conditionCollection\":[{\"isDefault\":true,\"id\":\"71e8050e-e063-4a35-b891-f38ddf63fa6d\",\"configuration\":{\"name\":\"Default\",\"output\":\"Default\",\"trigger\":\"all\",\"criteria\":[]},\"summary\":\"Default condition\"}]},\"composition\":[],\"telemetry\":{},\"modified\":1721850725791,\"location\":\"mine\",\"created\":1721850725791,\"persisted\":1721850725791},{\"identifier\":{\"key\":\"mine\",\"namespace\":\"\"},\"name\":\"My Items\",\"type\":\"folder\",\"composition\":[{\"key\":\"f64bea3b-58a7-4586-8c05-8b651e5f0bfd\",\"namespace\":\"\"},{\"key\":\"2d02a680-eb7e-4645-bba2-dd298f76efb8\",\"namespace\":\"\"},{\"key\":\"72a5f66b-39a7-4f62-8c40-4a99a33d6a8e\",\"namespace\":\"\"},{\"key\":\"8e4d20f1-9a04-4de5-8db5-c7e08d27f70d\",\"namespace\":\"\"},{\"key\":\"3e294eae-6124-409b-a870-554d1bdcdd6f\",\"namespace\":\"\"},{\"key\":\"ec24d05d-5df5-4c96-9241-b73636cd19a9\",\"namespace\":\"\"},{\"key\":\"0ec517e8-6c11-4d98-89b5-c300fe61b304\",\"namespace\":\"\"},{\"key\":\"ffb49de1-af27-4318-a22f-59899988f4e9\",\"namespace\":\"\"},{\"key\":\"c6c65ad5-5c09-43c2-8a12-fdeb64d3e1a4\",\"namespace\":\"\"},{\"key\":\"62c4ade7-85ce-45bd-8cdb-25f0c58c8a28\",\"namespace\":\"\"},{\"key\":\"c1717964-ffed-47aa-9ed9-647ba5a3db67\",\"namespace\":\"\"},{\"key\":\"10581641-5de3-4606-95aa-04cd811f2f53\",\"namespace\":\"\"},{\"key\":\"d9d79500-916d-4ff2-a7ea-6cf300c85ce3\",\"namespace\":\"\"}],\"location\":\"ROOT\",\"persisted\":1721850933441,\"modified\":1721850933441},{\"identifier\":{\"key\":\"ROOT\",\"namespace\":\"\"},\"name\":\"Open MCT\",\"type\":\"root\",\"composition\":[{\"key\":\"mine\",\"namespace\":\"\"}]}],\"navigationPath\":\"/browse/mine/10581641-5de3-4606-95aa-04cd811f2f53\",\"domainObject\":{\"identifier\":{\"key\":\"10581641-5de3-4606-95aa-04cd811f2f53\",\"namespace\":\"\"},\"name\":\"Unnamed Condition Set\",\"type\":\"conditionSet\",\"configuration\":{\"conditionTestData\":[],\"conditionCollection\":[{\"isDefault\":true,\"id\":\"71e8050e-e063-4a35-b891-f38ddf63fa6d\",\"configuration\":{\"name\":\"Default\",\"output\":\"Default\",\"trigger\":\"all\",\"criteria\":[]},\"summary\":\"Default condition\"}]},\"composition\":[],\"telemetry\":{},\"modified\":1721850725791,\"location\":\"mine\",\"created\":1721850725791,\"persisted\":1721850725791}},{\"objectPath\":[{\"identifier\":{\"key\":\"c1717964-ffed-47aa-9ed9-647ba5a3db67\",\"namespace\":\"\"},\"name\":\"Unnamed Condition Set\",\"type\":\"conditionSet\",\"configuration\":{\"conditionTestData\":[],\"conditionCollection\":[{\"isDefault\":true,\"id\":\"9a72ce62-012c-4a81-8f2c-51db5410de76\",\"configuration\":{\"name\":\"Default\",\"output\":\"Default\",\"trigger\":\"all\",\"criteria\":[]},\"summary\":\"Default condition\"}]},\"composition\":[],\"telemetry\":{},\"modified\":1721850626897,\"location\":\"mine\",\"created\":1721850626897,\"persisted\":1721850626897},{\"identifier\":{\"key\":\"mine\",\"namespace\":\"\"},\"name\":\"My Items\",\"type\":\"folder\",\"composition\":[{\"key\":\"f64bea3b-58a7-4586-8c05-8b651e5f0bfd\",\"namespace\":\"\"},{\"key\":\"2d02a680-eb7e-4645-bba2-dd298f76efb8\",\"namespace\":\"\"},{\"key\":\"72a5f66b-39a7-4f62-8c40-4a99a33d6a8e\",\"namespace\":\"\"},{\"key\":\"8e4d20f1-9a04-4de5-8db5-c7e08d27f70d\",\"namespace\":\"\"},{\"key\":\"3e294eae-6124-409b-a870-554d1bdcdd6f\",\"namespace\":\"\"},{\"key\":\"ec24d05d-5df5-4c96-9241-b73636cd19a9\",\"namespace\":\"\"},{\"key\":\"0ec517e8-6c11-4d98-89b5-c300fe61b304\",\"namespace\":\"\"},{\"key\":\"ffb49de1-af27-4318-a22f-59899988f4e9\",\"namespace\":\"\"},{\"key\":\"c6c65ad5-5c09-43c2-8a12-fdeb64d3e1a4\",\"namespace\":\"\"},{\"key\":\"62c4ade7-85ce-45bd-8cdb-25f0c58c8a28\",\"namespace\":\"\"},{\"key\":\"c1717964-ffed-47aa-9ed9-647ba5a3db67\",\"namespace\":\"\"},{\"key\":\"10581641-5de3-4606-95aa-04cd811f2f53\",\"namespace\":\"\"},{\"key\":\"d9d79500-916d-4ff2-a7ea-6cf300c85ce3\",\"namespace\":\"\"}],\"location\":\"ROOT\",\"persisted\":1721850933441,\"modified\":1721850933441},{\"identifier\":{\"key\":\"ROOT\",\"namespace\":\"\"},\"name\":\"Open MCT\",\"type\":\"root\",\"composition\":[{\"key\":\"mine\",\"namespace\":\"\"}]}],\"navigationPath\":\"/browse/mine/c1717964-ffed-47aa-9ed9-647ba5a3db67\",\"domainObject\":{\"identifier\":{\"key\":\"c1717964-ffed-47aa-9ed9-647ba5a3db67\",\"namespace\":\"\"},\"name\":\"Unnamed Condition Set\",\"type\":\"conditionSet\",\"configuration\":{\"conditionTestData\":[],\"conditionCollection\":[{\"isDefault\":true,\"id\":\"9a72ce62-012c-4a81-8f2c-51db5410de76\",\"configuration\":{\"name\":\"Default\",\"output\":\"Default\",\"trigger\":\"all\",\"criteria\":[]},\"summary\":\"Default condition\"}]},\"composition\":[],\"telemetry\":{},\"modified\":1721850626897,\"location\":\"mine\",\"created\":1721850626897,\"persisted\":1721850626897}},{\"objectPath\":[{\"identifier\":{\"key\":\"62c4ade7-85ce-45bd-8cdb-25f0c58c8a28\",\"namespace\":\"\"},\"name\":\"Unnamed Condition Set\",\"type\":\"conditionSet\",\"configuration\":{\"conditionTestData\":[],\"conditionCollection\":[{\"isDefault\":true,\"id\":\"6776a2ec-fa49-4b06-80ed-a6eaf4f86f56\",\"configuration\":{\"name\":\"Default\",\"output\":\"Default\",\"trigger\":\"all\",\"criteria\":[]},\"summary\":\"Default condition\"}]},\"composition\":[],\"telemetry\":{},\"modified\":1704955298729,\"location\":\"mine\",\"created\":1704955298729,\"persisted\":1704955298729},{\"identifier\":{\"key\":\"mine\",\"namespace\":\"\"},\"name\":\"My Items\",\"type\":\"folder\",\"composition\":[{\"key\":\"f64bea3b-58a7-4586-8c05-8b651e5f0bfd\",\"namespace\":\"\"},{\"key\":\"2d02a680-eb7e-4645-bba2-dd298f76efb8\",\"namespace\":\"\"},{\"key\":\"72a5f66b-39a7-4f62-8c40-4a99a33d6a8e\",\"namespace\":\"\"},{\"key\":\"8e4d20f1-9a04-4de5-8db5-c7e08d27f70d\",\"namespace\":\"\"},{\"key\":\"3e294eae-6124-409b-a870-554d1bdcdd6f\",\"namespace\":\"\"},{\"key\":\"ec24d05d-5df5-4c96-9241-b73636cd19a9\",\"namespace\":\"\"},{\"key\":\"0ec517e8-6c11-4d98-89b5-c300fe61b304\",\"namespace\":\"\"},{\"key\":\"ffb49de1-af27-4318-a22f-59899988f4e9\",\"namespace\":\"\"},{\"key\":\"c6c65ad5-5c09-43c2-8a12-fdeb64d3e1a4\",\"namespace\":\"\"},{\"key\":\"62c4ade7-85ce-45bd-8cdb-25f0c58c8a28\",\"namespace\":\"\"},{\"key\":\"c1717964-ffed-47aa-9ed9-647ba5a3db67\",\"namespace\":\"\"},{\"key\":\"10581641-5de3-4606-95aa-04cd811f2f53\",\"namespace\":\"\"},{\"key\":\"d9d79500-916d-4ff2-a7ea-6cf300c85ce3\",\"namespace\":\"\"}],\"location\":\"ROOT\",\"persisted\":1721850933441,\"modified\":1721850933441},{\"identifier\":{\"key\":\"ROOT\",\"namespace\":\"\"},\"name\":\"Open MCT\",\"type\":\"root\",\"composition\":[{\"key\":\"mine\",\"namespace\":\"\"}]}],\"navigationPath\":\"/browse/mine/62c4ade7-85ce-45bd-8cdb-25f0c58c8a28\",\"domainObject\":{\"identifier\":{\"key\":\"62c4ade7-85ce-45bd-8cdb-25f0c58c8a28\",\"namespace\":\"\"},\"name\":\"Unnamed Condition Set\",\"type\":\"conditionSet\",\"configuration\":{\"conditionTestData\":[],\"conditionCollection\":[{\"isDefault\":true,\"id\":\"6776a2ec-fa49-4b06-80ed-a6eaf4f86f56\",\"configuration\":{\"name\":\"Default\",\"output\":\"Default\",\"trigger\":\"all\",\"criteria\":[]},\"summary\":\"Default condition\"}]},\"composition\":[],\"telemetry\":{},\"modified\":1704955298729,\"location\":\"mine\",\"created\":1704955298729,\"persisted\":1704955298729}},{\"objectPath\":[{\"identifier\":{\"key\":\"c6c65ad5-5c09-43c2-8a12-fdeb64d3e1a4\",\"namespace\":\"\"},\"name\":\"Unnamed Condition Set\",\"type\":\"conditionSet\",\"configuration\":{\"conditionTestData\":[],\"conditionCollection\":[{\"isDefault\":true,\"id\":\"6c8bdbec-41f5-4138-a88f-ae6ebdbc9a90\",\"configuration\":{\"name\":\"Default\",\"output\":\"Default\",\"trigger\":\"all\",\"criteria\":[]},\"summary\":\"Default condition\"}]},\"composition\":[],\"telemetry\":{},\"modified\":1694112912985,\"location\":\"mine\",\"created\":1694112912985,\"persisted\":1694112912985},{\"identifier\":{\"key\":\"mine\",\"namespace\":\"\"},\"name\":\"My Items\",\"type\":\"folder\",\"composition\":[{\"key\":\"f64bea3b-58a7-4586-8c05-8b651e5f0bfd\",\"namespace\":\"\"},{\"key\":\"2d02a680-eb7e-4645-bba2-dd298f76efb8\",\"namespace\":\"\"},{\"key\":\"72a5f66b-39a7-4f62-8c40-4a99a33d6a8e\",\"namespace\":\"\"},{\"key\":\"8e4d20f1-9a04-4de5-8db5-c7e08d27f70d\",\"namespace\":\"\"},{\"key\":\"3e294eae-6124-409b-a870-554d1bdcdd6f\",\"namespace\":\"\"},{\"key\":\"ec24d05d-5df5-4c96-9241-b73636cd19a9\",\"namespace\":\"\"},{\"key\":\"0ec517e8-6c11-4d98-89b5-c300fe61b304\",\"namespace\":\"\"},{\"key\":\"ffb49de1-af27-4318-a22f-59899988f4e9\",\"namespace\":\"\"},{\"key\":\"c6c65ad5-5c09-43c2-8a12-fdeb64d3e1a4\",\"namespace\":\"\"},{\"key\":\"62c4ade7-85ce-45bd-8cdb-25f0c58c8a28\",\"namespace\":\"\"},{\"key\":\"c1717964-ffed-47aa-9ed9-647ba5a3db67\",\"namespace\":\"\"},{\"key\":\"10581641-5de3-4606-95aa-04cd811f2f53\",\"namespace\":\"\"},{\"key\":\"d9d79500-916d-4ff2-a7ea-6cf300c85ce3\",\"namespace\":\"\"}],\"location\":\"ROOT\",\"persisted\":1721850933441,\"modified\":1721850933441},{\"identifier\":{\"key\":\"ROOT\",\"namespace\":\"\"},\"name\":\"Open MCT\",\"type\":\"root\",\"composition\":[{\"key\":\"mine\",\"namespace\":\"\"}]}],\"navigationPath\":\"/browse/mine/c6c65ad5-5c09-43c2-8a12-fdeb64d3e1a4\",\"domainObject\":{\"identifier\":{\"key\":\"c6c65ad5-5c09-43c2-8a12-fdeb64d3e1a4\",\"namespace\":\"\"},\"name\":\"Unnamed Condition Set\",\"type\":\"conditionSet\",\"configuration\":{\"conditionTestData\":[],\"conditionCollection\":[{\"isDefault\":true,\"id\":\"6c8bdbec-41f5-4138-a88f-ae6ebdbc9a90\",\"configuration\":{\"name\":\"Default\",\"output\":\"Default\",\"trigger\":\"all\",\"criteria\":[]},\"summary\":\"Default condition\"}]},\"composition\":[],\"telemetry\":{},\"modified\":1694112912985,\"location\":\"mine\",\"created\":1694112912985,\"persisted\":1694112912985}},{\"objectPath\":[{\"identifier\":{\"key\":\"ffb49de1-af27-4318-a22f-59899988f4e9\",\"namespace\":\"\"},\"name\":\"Unnamed Condition Set\",\"type\":\"conditionSet\",\"configuration\":{\"conditionTestData\":[],\"conditionCollection\":[{\"isDefault\":true,\"id\":\"0a04f110-e5c4-4503-9276-6e8f783d5bd5\",\"configuration\":{\"name\":\"Default\",\"output\":\"Default\",\"trigger\":\"all\",\"criteria\":[]},\"summary\":\"Default condition\"}]},\"composition\":[],\"telemetry\":{},\"modified\":1694110416938,\"location\":\"mine\",\"created\":1694110416938,\"persisted\":1694110416938},{\"identifier\":{\"key\":\"mine\",\"namespace\":\"\"},\"name\":\"My Items\",\"type\":\"folder\",\"composition\":[{\"key\":\"f64bea3b-58a7-4586-8c05-8b651e5f0bfd\",\"namespace\":\"\"},{\"key\":\"2d02a680-eb7e-4645-bba2-dd298f76efb8\",\"namespace\":\"\"},{\"key\":\"72a5f66b-39a7-4f62-8c40-4a99a33d6a8e\",\"namespace\":\"\"},{\"key\":\"8e4d20f1-9a04-4de5-8db5-c7e08d27f70d\",\"namespace\":\"\"},{\"key\":\"3e294eae-6124-409b-a870-554d1bdcdd6f\",\"namespace\":\"\"},{\"key\":\"ec24d05d-5df5-4c96-9241-b73636cd19a9\",\"namespace\":\"\"},{\"key\":\"0ec517e8-6c11-4d98-89b5-c300fe61b304\",\"namespace\":\"\"},{\"key\":\"ffb49de1-af27-4318-a22f-59899988f4e9\",\"namespace\":\"\"},{\"key\":\"c6c65ad5-5c09-43c2-8a12-fdeb64d3e1a4\",\"namespace\":\"\"},{\"key\":\"62c4ade7-85ce-45bd-8cdb-25f0c58c8a28\",\"namespace\":\"\"},{\"key\":\"c1717964-ffed-47aa-9ed9-647ba5a3db67\",\"namespace\":\"\"},{\"key\":\"10581641-5de3-4606-95aa-04cd811f2f53\",\"namespace\":\"\"},{\"key\":\"d9d79500-916d-4ff2-a7ea-6cf300c85ce3\",\"namespace\":\"\"}],\"location\":\"ROOT\",\"persisted\":1721850933441,\"modified\":1721850933441},{\"identifier\":{\"key\":\"ROOT\",\"namespace\":\"\"},\"name\":\"Open MCT\",\"type\":\"root\",\"composition\":[{\"key\":\"mine\",\"namespace\":\"\"}]}],\"navigationPath\":\"/browse/mine/ffb49de1-af27-4318-a22f-59899988f4e9\",\"domainObject\":{\"identifier\":{\"key\":\"ffb49de1-af27-4318-a22f-59899988f4e9\",\"namespace\":\"\"},\"name\":\"Unnamed Condition Set\",\"type\":\"conditionSet\",\"configuration\":{\"conditionTestData\":[],\"conditionCollection\":[{\"isDefault\":true,\"id\":\"0a04f110-e5c4-4503-9276-6e8f783d5bd5\",\"configuration\":{\"name\":\"Default\",\"output\":\"Default\",\"trigger\":\"all\",\"criteria\":[]},\"summary\":\"Default condition\"}]},\"composition\":[],\"telemetry\":{},\"modified\":1694110416938,\"location\":\"mine\",\"created\":1694110416938,\"persisted\":1694110416938}},{\"objectPath\":[{\"identifier\":{\"key\":\"0ec517e8-6c11-4d98-89b5-c300fe61b304\",\"namespace\":\"\"},\"name\":\"Unnamed Condition Set\",\"type\":\"conditionSet\",\"configuration\":{\"conditionTestData\":[],\"conditionCollection\":[{\"isDefault\":true,\"id\":\"2f1585da-6f7e-4ccd-8a20-590fdf177b5d\",\"configuration\":{\"name\":\"Default\",\"output\":\"Default\",\"trigger\":\"all\",\"criteria\":[]},\"summary\":\"Default condition\"}]},\"composition\":[],\"telemetry\":{},\"modified\":1689710689550,\"location\":\"mine\",\"created\":1689710689550,\"persisted\":1689710689550},{\"identifier\":{\"key\":\"mine\",\"namespace\":\"\"},\"name\":\"My Items\",\"type\":\"folder\",\"composition\":[{\"key\":\"f64bea3b-58a7-4586-8c05-8b651e5f0bfd\",\"namespace\":\"\"},{\"key\":\"2d02a680-eb7e-4645-bba2-dd298f76efb8\",\"namespace\":\"\"},{\"key\":\"72a5f66b-39a7-4f62-8c40-4a99a33d6a8e\",\"namespace\":\"\"},{\"key\":\"8e4d20f1-9a04-4de5-8db5-c7e08d27f70d\",\"namespace\":\"\"},{\"key\":\"3e294eae-6124-409b-a870-554d1bdcdd6f\",\"namespace\":\"\"},{\"key\":\"ec24d05d-5df5-4c96-9241-b73636cd19a9\",\"namespace\":\"\"},{\"key\":\"0ec517e8-6c11-4d98-89b5-c300fe61b304\",\"namespace\":\"\"},{\"key\":\"ffb49de1-af27-4318-a22f-59899988f4e9\",\"namespace\":\"\"},{\"key\":\"c6c65ad5-5c09-43c2-8a12-fdeb64d3e1a4\",\"namespace\":\"\"},{\"key\":\"62c4ade7-85ce-45bd-8cdb-25f0c58c8a28\",\"namespace\":\"\"},{\"key\":\"c1717964-ffed-47aa-9ed9-647ba5a3db67\",\"namespace\":\"\"},{\"key\":\"10581641-5de3-4606-95aa-04cd811f2f53\",\"namespace\":\"\"},{\"key\":\"d9d79500-916d-4ff2-a7ea-6cf300c85ce3\",\"namespace\":\"\"}],\"location\":\"ROOT\",\"persisted\":1721850933441,\"modified\":1721850933441},{\"identifier\":{\"key\":\"ROOT\",\"namespace\":\"\"},\"name\":\"Open MCT\",\"type\":\"root\",\"composition\":[{\"key\":\"mine\",\"namespace\":\"\"}]}],\"navigationPath\":\"/browse/mine/0ec517e8-6c11-4d98-89b5-c300fe61b304\",\"domainObject\":{\"identifier\":{\"key\":\"0ec517e8-6c11-4d98-89b5-c300fe61b304\",\"namespace\":\"\"},\"name\":\"Unnamed Condition Set\",\"type\":\"conditionSet\",\"configuration\":{\"conditionTestData\":[],\"conditionCollection\":[{\"isDefault\":true,\"id\":\"2f1585da-6f7e-4ccd-8a20-590fdf177b5d\",\"configuration\":{\"name\":\"Default\",\"output\":\"Default\",\"trigger\":\"all\",\"criteria\":[]},\"summary\":\"Default condition\"}]},\"composition\":[],\"telemetry\":{},\"modified\":1689710689550,\"location\":\"mine\",\"created\":1689710689550,\"persisted\":1689710689550}}]" } ] } diff --git a/e2e/tests/framework/appActions.e2e.spec.js b/e2e/tests/framework/appActions.e2e.spec.js index d58d73968c..280ef33f17 100644 --- a/e2e/tests/framework/appActions.e2e.spec.js +++ b/e2e/tests/framework/appActions.e2e.spec.js @@ -19,19 +19,29 @@ * this source code distribution or the Licensing information page available * at runtime from the About dialog for additional information. *****************************************************************************/ +import fs from 'fs'; import { createDomainObjectWithDefaults, + createExampleTelemetryObject, createNotification, + createPlanFromJSON, expandEntireTree, - openObjectTreeContextMenu, + getCanvasPixels, + navigateToObjectWithFixedTimeBounds, + navigateToObjectWithRealTime, + setEndOffset, + setFixedIndependentTimeConductorBounds, setFixedTimeMode, setRealTimeMode, - setTimeConductorBounds + setStartOffset, + setTimeConductorBounds, + waitForPlotsToRender } from '../../appActions.js'; +import { assertPlanActivities, setBoundsToSpanAllActivities } from '../../helper/planningUtils.js'; import { expect, test } from '../../pluginFixtures.js'; -test.describe('AppActions', () => { +test.describe('AppActions @framework', () => { test.beforeEach(async ({ page }) => { await page.goto('./', { waitUntil: 'domcontentloaded' }); }); @@ -94,6 +104,38 @@ test.describe('AppActions', () => { expect(folder3.url).toBe(`${e2eFolder.url}/${folder1.uuid}/${folder2.uuid}/${folder3.uuid}`); }); }); + test('createExampleTelemetryObject', async ({ page }) => { + const gauge = await createDomainObjectWithDefaults(page, { + type: 'Gauge', + name: 'Gauge with no data' + }); + + const swgWithParent = await createExampleTelemetryObject(page, gauge.uuid); + + await page.goto(swgWithParent.url); + await expect(page.locator('.l-browse-bar__object-name')).toHaveText(swgWithParent.name); + await page.getByLabel('More actions').click(); + await page.getByLabel('Edit Properties...').click(); + + // Check Default values of created object + await expect(page.getByLabel('Title', { exact: true })).toHaveValue('VIPER Rover Heading'); + await expect(page.getByRole('spinbutton', { name: 'Period' })).toHaveValue('10'); + await expect(page.getByRole('spinbutton', { name: 'Amplitude' })).toHaveValue('1'); + await expect(page.getByRole('spinbutton', { name: 'Offset' })).toHaveValue('0'); + await expect(page.getByRole('spinbutton', { name: 'Data Rate (hz)' })).toHaveValue('1'); + await expect(page.getByRole('spinbutton', { name: 'Phase (radians)' })).toHaveValue('0'); + await expect(page.getByRole('spinbutton', { name: 'Randomness' })).toHaveValue('0'); + await expect(page.getByRole('spinbutton', { name: 'Loading Delay (ms)' })).toHaveValue('0'); + + await page.getByLabel('Cancel').click(); + + const swgWithoutParent = await createExampleTelemetryObject(page); + + await page.getByLabel('Show selected item in tree').click(); + + expect(swgWithParent.url).toBe(`${gauge.url}/${swgWithParent.uuid}`); + expect(swgWithoutParent.url).toBe(`./#/browse/mine/${swgWithoutParent.uuid}`); + }); test('createNotification', async ({ page }) => { await createNotification(page, { message: 'Test info notification', @@ -117,6 +159,19 @@ test.describe('AppActions', () => { await expect(page.locator('.c-message-banner')).toHaveClass(/error/); await page.locator('[aria-label="Dismiss"]').click(); }); + test('createPlanFromJSON', async ({ page }) => { + const examplePlanSmall1 = JSON.parse( + fs.readFileSync( + new URL('../../test-data/examplePlans/ExamplePlan_Small1.json', import.meta.url) + ) + ); + const plan = await createPlanFromJSON(page, { + name: 'Test Plan', + json: examplePlanSmall1 + }); + await setBoundsToSpanAllActivities(page, examplePlanSmall1, plan.url); + await assertPlanActivities(page, examplePlanSmall1, plan.url); + }); test('expandEntireTree', async ({ page }) => { const rootFolder = await createDomainObjectWithDefaults(page, { type: 'Folder' @@ -153,46 +208,135 @@ test.describe('AppActions', () => { name: 'Main Tree' }); const treePaneCollapsedItems = treePane.getByRole('treeitem', { expanded: false }); - expect(await treePaneCollapsedItems.count()).toBe(0); + await expect(treePaneCollapsedItems).toHaveCount(0); await page.goto('./#/browse/mine'); //Click the Create button await page.getByRole('button', { name: 'Create' }).click(); // Click the object specified by 'type' - await page.click(`li[role='menuitem']:text("Clock")`); + await page.getByRole('menuitem', { name: 'Clock' }).click(); await expandEntireTree(page, 'Create Modal Tree'); const locatorTree = page.getByRole('tree', { name: 'Create Modal Tree' }); const locatorTreeCollapsedItems = locatorTree.locator('role=treeitem[expanded=false]'); - expect(await locatorTreeCollapsedItems.count()).toBe(0); + await expect(locatorTreeCollapsedItems).toHaveCount(0); }); - test('openObjectTreeContextMenu', async ({ page }) => { - const folder = await createDomainObjectWithDefaults(page, { - type: 'Folder' + test('getCanvasPixels', async ({ page }) => { + let overlayPlot = await createDomainObjectWithDefaults(page, { + type: 'Overlay Plot' }); - await openObjectTreeContextMenu(page, folder.url); - await expect(page.getByLabel(`${folder.name} Context Menu`)).toBeVisible(); + + await createExampleTelemetryObject(page, overlayPlot.uuid); + + await page.goto(overlayPlot.url); + //Get pixel data from Canvas + const plotPixels = await getCanvasPixels(page, 'canvas'); + const plotPixelSize = plotPixels.length; + expect(plotPixelSize).toBeGreaterThan(0); }); - test('setTimeConductorMode', async ({ page }) => { - await setFixedTimeMode(page); + test('navigateToObjectWithFixedTimeBounds', async ({ page }) => { + const exampleTelemetry = await createExampleTelemetryObject(page); + //Navigate without explicit bounds + await navigateToObjectWithFixedTimeBounds(page, exampleTelemetry.url); await expect(page.getByLabel('Start bounds:')).toBeVisible(); await expect(page.getByLabel('End bounds:')).toBeVisible(); - await setRealTimeMode(page); - await expect(page.getByLabel('Start offset')).toBeVisible(); - await expect(page.getByLabel('End offset')).toBeVisible(); + //Navigate with explicit bounds + await navigateToObjectWithFixedTimeBounds( + page, + exampleTelemetry.url, + 1693592063607, + 1693593893607 + ); + await expect(page.getByLabel('Start bounds: 2023-09-01 18:')).toBeVisible(); + await expect(page.getByLabel('End bounds: 2023-09-01 18:44:')).toBeVisible(); }); - test('setTimeConductorBounds', async ({ page }) => { - // Assume in real-time mode by default - await setFixedTimeMode(page); - await setTimeConductorBounds(page, { - startDate: '2024-01-01', - endDate: '2024-01-02', - startTime: '00:00:00', - endTime: '23:59:59' + test('navigateToObjectWithRealTime', async ({ page }) => { + const exampleTelemetry = await createExampleTelemetryObject(page); + //Navigate without explicit bounds + await navigateToObjectWithRealTime(page, exampleTelemetry.url); + await expect(page.getByLabel('Start offset:')).toBeVisible(); + await expect(page.getByLabel('End offset: 00:00:')).toBeVisible(); + //Navigate with explicit bounds + await navigateToObjectWithRealTime(page, exampleTelemetry.url, 1693592063607, 1693593893607); + await expect(page.getByLabel('Start offset: 18:14:')).toBeVisible(); + await expect(page.getByLabel('End offset: 18:44:')).toBeVisible(); + }); + test('setTimeConductorMode', async ({ page }) => { + await test.step('setFixedTimeMode', async () => { + await setFixedTimeMode(page); + await expect(page.getByLabel('Start bounds:')).toBeVisible(); + await expect(page.getByLabel('End bounds:')).toBeVisible(); }); - await expect(page.getByLabel('Start bounds: 2024-01-01 00:00:00')).toBeVisible(); - await expect(page.getByLabel('End bounds: 2024-01-02 23:59:59')).toBeVisible(); + await test.step('setTimeConductorBounds', async () => { + await setTimeConductorBounds(page, { + startDate: '2024-01-01', + endDate: '2024-01-02', + startTime: '00:00:00', + endTime: '23:59:59' + }); + await expect(page.getByLabel('Start bounds: 2024-01-01 00:00:00')).toBeVisible(); + await expect(page.getByLabel('End bounds: 2024-01-02 23:59:59')).toBeVisible(); + }); + await test.step('setRealTimeMode', async () => { + await setRealTimeMode(page); + await expect(page.getByLabel('Start offset')).toBeVisible(); + await expect(page.getByLabel('End offset')).toBeVisible(); + }); + await test.step('setStartOffset', async () => { + await setStartOffset(page, { + startHours: '04', + startMins: '20', + startSecs: '22' + }); + await expect(page.getByLabel('Start offset: 04:20:22')).toBeVisible(); + }); + await test.step('setEndOffset', async () => { + await setEndOffset(page, { + endHours: '04', + endMins: '20', + endSecs: '22' + }); + await expect(page.getByLabel('End offset: 04:20:22')).toBeVisible(); + }); + }); + test('setFixedIndependentTimeConductorBounds', async ({ page }) => { + // Create a Display Layout + const displayLayout = await createDomainObjectWithDefaults(page, { + type: 'Display Layout' + }); + await createDomainObjectWithDefaults(page, { + type: 'Example Imagery', + parent: displayLayout.uuid + }); + + const startDate = '2021-12-30 01:01:00.000Z'; + const endDate = '2021-12-30 01:11:00.000Z'; + await setFixedIndependentTimeConductorBounds(page, { start: startDate, end: endDate }); + + // check image date + await expect(page.getByText('2021-12-30 01:11:00.000Z').first()).toBeVisible(); + + // flip it off + await page.getByRole('switch').click(); + // timestamp shouldn't be in the past anymore + await expect(page.getByText('2021-12-30 01:11:00.000Z')).toBeHidden(); + }); + test.fail('waitForPlotsToRender', async ({ page }) => { + // Create a SWG + await createDomainObjectWithDefaults(page, { + type: 'Sine Wave Generator' + }); + // Edit the SWG + await page.getByLabel('More actions').click(); + await page.getByLabel('Edit Properties...').click(); + // Set loading delay to 10 seconds + await page.getByLabel('Loading Delay (ms)', { exact: true }).fill('10000'); + await page.getByLabel('Save').click(); + // Reload the page + await page.reload(); + // Expect this step to fail + await waitForPlotsToRender(page, { timeout: 1000 }); }); }); diff --git a/e2e/tests/framework/baseFixtures.e2e.spec.js b/e2e/tests/framework/baseFixtures.e2e.spec.js index 133f5f5604..337a650ef5 100644 --- a/e2e/tests/framework/baseFixtures.e2e.spec.js +++ b/e2e/tests/framework/baseFixtures.e2e.spec.js @@ -26,7 +26,7 @@ relates to how we've extended it (i.e. ./e2e/baseFixtures.js) and assumptions ma (`npm start` and ./e2e/webpack-dev-middleware.js) */ -import { expect, test } from '../../baseFixtures.js'; +import { test } from '../../baseFixtures.js'; import { MISSION_TIME } from '../../constants.js'; test.describe('baseFixtures tests', () => { @@ -42,6 +42,21 @@ test.describe('baseFixtures tests', () => { page.waitForEvent('console') // always wait for the event to happen while triggering it! ]); }); + test('Verify that tests fail if console.error is thrown with clock override @clock', async ({ + page + }) => { + test.fail(); + //Set clock time + await page.clock.install({ time: MISSION_TIME }); + await page.clock.resume(); + await page.goto('./', { waitUntil: 'domcontentloaded' }); + + //Verify that ../fixtures.js detects console log errors + await Promise.all([ + page.evaluate(() => console.error('This should result in a failure')), + page.waitForEvent('console') // always wait for the event to happen while triggering it! + ]); + }); test('Verify that tests pass if console.warn is thrown', async ({ page }) => { //Go to baseURL await page.goto('./', { waitUntil: 'domcontentloaded' }); @@ -53,27 +68,3 @@ test.describe('baseFixtures tests', () => { ]); }); }); - -test.describe('baseFixtures tests @clock', () => { - test.use({ - clockOptions: { - now: MISSION_TIME, - shouldAdvanceTime: false - } - }); - - test.beforeEach(async ({ page }) => { - await page.goto('./', { waitUntil: 'domcontentloaded' }); - }); - - test('Can use clockOptions and tick fixtures to control the clock', async ({ page, tick }) => { - let time = await page.evaluate(() => new Date().getTime()); - expect(time).toBe(MISSION_TIME); - await tick(1000); - time = await page.evaluate(() => new Date().getTime()); - expect(time).toBe(MISSION_TIME + 1000 * 1); - await tick(1000); - time = await page.evaluate(() => new Date().getTime()); - expect(time).toBe(MISSION_TIME + 1000 * 2); - }); -}); diff --git a/e2e/tests/framework/exampleTemplate.e2e.spec.js b/e2e/tests/framework/exampleTemplate.e2e.spec.js index fa696b365f..631be082b1 100644 --- a/e2e/tests/framework/exampleTemplate.e2e.spec.js +++ b/e2e/tests/framework/exampleTemplate.e2e.spec.js @@ -30,7 +30,6 @@ * Demonstrated: * - Using appActions to leverage existing functions * - Structure - * - @unstable annotation * - await, expect, test, describe syntax * - Writing a custom function for a test suite * - Test stub for unfinished test coverage (test.fixme) @@ -45,7 +44,7 @@ */ // Structure: Some standard Imports. Please update the required pathing. -import { createDomainObjectWithDefaults } from '../../appActions.js'; +import { createDomainObjectWithDefaults, createExampleTelemetryObject } from '../../appActions.js'; import { expect, test } from '../../pluginFixtures.js'; /** @@ -53,11 +52,8 @@ import { expect, test } from '../../pluginFixtures.js'; * Try to keep a single describe block per logical groups of tests. * If your test runtime exceeds 5 minutes or 500 lines, it's likely that it will need to be split. * - * Annotations: - * Please use the @unstable tag at the end of the test title so that our automation can pick it up - * as a part of our test promotion pipeline. */ -test.describe('Renaming Timer Object', () => { +test.describe('Example - Renaming Timer Object', () => { // Top-level declaration of the Timer object created in beforeEach(). // We can then use this throughout the entire test suite. let timer; @@ -70,7 +66,7 @@ test.describe('Renaming Timer Object', () => { timer = await createDomainObjectWithDefaults(page, { type: 'Timer' }); // Assert the object to be created and check its name in the title - await expect(page.locator('.l-browse-bar__object-name')).toContainText(timer.name); + await expect(page.getByRole('main')).toContainText(timer.name); }); /** @@ -85,7 +81,7 @@ test.describe('Renaming Timer Object', () => { await renameTimerFrom3DotMenu(page, timer.url, newObjectName); // Assert that the name has changed in the browser bar to the value we assigned above - await expect(page.locator('.l-browse-bar__object-name')).toContainText(newObjectName); + await expect(page.getByRole('main')).toContainText(newObjectName); }); test('An existing Timer object can be renamed twice', async ({ page }) => { @@ -95,13 +91,13 @@ test.describe('Renaming Timer Object', () => { await renameTimerFrom3DotMenu(page, timer.url, newObjectName); // Assert that the name has changed in the browser bar to the value we assigned above - await expect(page.locator('.l-browse-bar__object-name')).toContainText(newObjectName); + await expect(page.getByRole('main')).toContainText(newObjectName); // Rename the Timer object again await renameTimerFrom3DotMenu(page, timer.url, newObjectName2); // Assert that the name has changed in the browser bar to the second value - await expect(page.locator('.l-browse-bar__object-name')).toContainText(newObjectName2); + await expect(page.getByRole('main')).toContainText(newObjectName2); }); /** @@ -121,7 +117,7 @@ test.describe('Renaming Timer Object', () => { * The next most important concept in our testing is working with telemetry objects. Telemetry is at the core of Open MCT * and we have developed a great pattern for working with it. */ -test.describe('Advanced: Working with telemetry objects', () => { +test.describe('Advanced Example - Working with telemetry objects', () => { let displayLayout; test.beforeEach(async ({ page }) => { await page.goto('./', { waitUntil: 'domcontentloaded' }); @@ -132,17 +128,14 @@ test.describe('Advanced: Working with telemetry objects', () => { name: 'Display Layout with Embedded SWG' }); // Create Telemetry object within the parent object created above - await createDomainObjectWithDefaults(page, { - type: 'Sine Wave Generator', - name: 'Telemetry', - parent: displayLayout.uuid //reference the display layout in the creation process - }); + //reference the display layout in the creation process + await createExampleTelemetryObject(page, displayLayout.uuid); }); test('Can directly navigate to a Display Layout with embedded telemetry', async ({ page }) => { //Now you can directly navigate to the displayLayout created in the beforeEach with the embedded telemetry await page.goto(displayLayout.url); //Expect the created Telemetry Object to be visible when directly navigating to the displayLayout - await expect(page.getByTitle('Sine')).toBeVisible(); + await expect(page.getByLabel('Alpha-numeric telemetry name')).toBeVisible(); }); }); @@ -160,18 +153,14 @@ test.describe('Advanced: Working with telemetry objects', () => { * @param {string} newNameForTimer New name for object */ async function renameTimerFrom3DotMenu(page, timerUrl, newNameForTimer) { - // Navigate to the timer object + // Navigate to the timer object directly await page.goto(timerUrl); - // Click on 3 Dot Menu - await page.locator('button[title="More actions"]').click(); - - // Click text=Edit Properties... - await page.locator('text=Edit Properties...').click(); + await page.getByLabel('More actions').click(); + await page.getByLabel('Edit Properties...').click(); // Rename the timer object - await page.locator('text=Properties Title Notes >> input[type="text"]').fill(newNameForTimer); + await page.getByLabel('Title', { exact: true }).fill(newNameForTimer); - // Click Ok button to Save - await page.locator('button:has-text("OK")').click(); + await page.getByLabel('Save').click(); } diff --git a/e2e/tests/framework/generateLocalStorageData.e2e.spec.js b/e2e/tests/framework/generateLocalStorageData.e2e.spec.js index ee9ff5d73b..544a04e25f 100644 --- a/e2e/tests/framework/generateLocalStorageData.e2e.spec.js +++ b/e2e/tests/framework/generateLocalStorageData.e2e.spec.js @@ -36,7 +36,7 @@ import { fileURLToPath } from 'url'; import { createDomainObjectWithDefaults, createExampleTelemetryObject, - setIndependentTimeConductorBounds, + setFixedIndependentTimeConductorBounds, setTimeConductorBounds } from '../../appActions.js'; import { MISSION_TIME } from '../../constants.js'; @@ -45,14 +45,10 @@ import { expect, test } from '../../pluginFixtures.js'; const overlayPlotName = 'Overlay Plot with Telemetry Object'; test.describe('Generate Visual Test Data @localStorage @generatedata @clock', () => { - test.use({ - clockOptions: { - now: MISSION_TIME, - shouldAdvanceTime: true - } - }); - test.beforeEach(async ({ page }) => { + // Override the clock + await page.clock.install({ time: MISSION_TIME }); + await page.clock.resume(); // Go to baseURL await page.goto('./', { waitUntil: 'domcontentloaded' }); }); @@ -112,7 +108,7 @@ test.describe('Generate Visual Test Data @localStorage @generatedata @clock', () await page.goto(parent.url, { waitUntil: 'domcontentloaded' }); - await setIndependentTimeConductorBounds(page, { + await setFixedIndependentTimeConductorBounds(page, { start: '2024-11-12 19:11:11.000Z', end: '2024-11-12 20:11:11.000Z' }); @@ -211,11 +207,7 @@ test.describe('Generate Visual Test Data @localStorage @generatedata @clock', () // TODO: Flesh Out Assertions against created Objects await expect(page.locator('.l-browse-bar__object-name')).toContainText(overlayPlotName); await page.getByRole('tab', { name: 'Config' }).click(); - await page - .getByRole('list', { name: 'Plot Series Properties' }) - .locator('span') - .first() - .click(); + await page.getByLabel('Plot Series Items').getByLabel('Expand').click(); // TODO: Modify the Overlay Plot to use fixed Scaling // TODO: Verify Autoscaling. @@ -274,7 +266,7 @@ test.describe('Generate Visual Test Data @localStorage @generatedata @clock', () page.waitForNavigation(), page.locator('text=OK').click(), //Wait for Save Banner to appear - page.waitForSelector('.c-message-banner__message') + page.locator('.c-message-banner__message').hover({ trial: true }) ]); // focus the overlay plot @@ -284,7 +276,7 @@ test.describe('Generate Visual Test Data @localStorage @generatedata @clock', () // Clear Recently Viewed await page.getByRole('button', { name: 'Clear Recently Viewed' }).click(); - await page.getByRole('button', { name: 'OK', exact: true }).click(); + await page.getByRole('button', { name: 'Ok', exact: true }).click(); //Save localStorage for future test execution await context.storageState({ path: fileURLToPath( @@ -307,11 +299,7 @@ test.describe('Validate Overlay Plot with Telemetry Object @localStorage @genera // TODO: Flesh Out Assertions against created Objects await expect(page.locator('.l-browse-bar__object-name')).toContainText(overlayPlotName); await page.getByRole('tab', { name: 'Config' }).click(); - await page - .getByRole('list', { name: 'Plot Series Properties' }) - .locator('span') - .first() - .click(); + await page.getByLabel('Plot Series Items').getByLabel('Expand').click(); // TODO: Modify the Overlay Plot to use fixed Scaling // TODO: Verify Autoscaling. @@ -352,11 +340,7 @@ test.describe('Validate Overlay Plot with 5s Delay Telemetry Object @localStorag // TODO: Flesh Out Assertions against created Objects await expect(page.locator('.l-browse-bar__object-name')).toContainText(plotName); await page.getByRole('tab', { name: 'Config' }).click(); - await page - .getByRole('list', { name: 'Plot Series Properties' }) - .locator('span') - .first() - .click(); + await page.getByLabel('Plot Series Items').getByLabel('Expand').click(); // TODO: Modify the Overlay Plot to use fixed Scaling // TODO: Verify Autoscaling. diff --git a/e2e/tests/framework/pluginFixtures.e2e.spec.js b/e2e/tests/framework/pluginFixtures.e2e.spec.js index f9b0cb5a7b..e1eaa637d0 100644 --- a/e2e/tests/framework/pluginFixtures.e2e.spec.js +++ b/e2e/tests/framework/pluginFixtures.e2e.spec.js @@ -31,7 +31,7 @@ import { test } from '../../pluginFixtures.js'; test.describe.skip('pluginFixtures tests', () => { // test.use({ domainObjectName: 'Timer' }); // let timerUUID; - // test('Creates a timer object @framework @unstable', ({ domainObject }) => { + // test('Creates a timer object @framework', ({ domainObject }) => { // const { uuid } = domainObject; // const uuidRegexp = /[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}/; // expect(uuid).toMatch(uuidRegexp); diff --git a/e2e/tests/functional/clearDataAction.e2e.spec.js b/e2e/tests/functional/clearDataAction.e2e.spec.js index 56e6b2f274..0e3165276b 100644 --- a/e2e/tests/functional/clearDataAction.e2e.spec.js +++ b/e2e/tests/functional/clearDataAction.e2e.spec.js @@ -59,6 +59,6 @@ test.describe('Clear Data Action', () => { // Verify that the background image is no longer visible await expect(page.locator(backgroundImageSelector)).toBeHidden(); - expect(await page.locator('.c-thumb__image').count()).toBe(0); + await expect(page.locator('.c-thumb__image')).toHaveCount(0); }); }); diff --git a/e2e/tests/functional/couchdb.e2e.spec.js b/e2e/tests/functional/couchdb.e2e.spec.js index b4042e8eaf..3afa9033e2 100644 --- a/e2e/tests/functional/couchdb.e2e.spec.js +++ b/e2e/tests/functional/couchdb.e2e.spec.js @@ -41,7 +41,7 @@ test.describe('CouchDB Status Indicator with mocked responses @couchdb', () => { //Go to baseURL await page.goto('./#/browse/mine?hideTree=true&hideInspector=true', { - waitUntil: 'networkidle' + waitUntil: 'domcontentloaded' }); await expect(page.locator('div:has-text("CouchDB is connected")').nth(3)).toBeVisible(); }); @@ -56,7 +56,7 @@ test.describe('CouchDB Status Indicator with mocked responses @couchdb', () => { //Go to baseURL await page.goto('./#/browse/mine?hideTree=true&hideInspector=true', { - waitUntil: 'networkidle' + waitUntil: 'domcontentloaded' }); await expect(page.locator('div:has-text("CouchDB is offline")').nth(3)).toBeVisible(); }); @@ -71,7 +71,7 @@ test.describe('CouchDB Status Indicator with mocked responses @couchdb', () => { //Go to baseURL await page.goto('./#/browse/mine?hideTree=true&hideInspector=true', { - waitUntil: 'networkidle' + waitUntil: 'domcontentloaded' }); await expect(page.locator('div:has-text("CouchDB connectivity unknown")').nth(3)).toBeVisible(); }); diff --git a/e2e/tests/functional/example/generator/sineWaveLimitProvider.e2e.spec.js b/e2e/tests/functional/example/generator/sineWaveLimitProvider.e2e.spec.js index a0c50055b4..ca50314164 100644 --- a/e2e/tests/functional/example/generator/sineWaveLimitProvider.e2e.spec.js +++ b/e2e/tests/functional/example/generator/sineWaveLimitProvider.e2e.spec.js @@ -41,7 +41,7 @@ test.describe('Sine Wave Generator', () => { await page.getByRole('button', { name: 'Create' }).click(); // Click Sine Wave Generator - await page.click('text=Sine Wave Generator'); + await page.getByRole('menuitem', { name: 'Sine Wave Generator' }).click(); // Verify that the each required field has required indicator // Title @@ -107,11 +107,11 @@ test.describe('Sine Wave Generator', () => { await page.locator('.field.control.l-input-sm input').first().press('ArrowUp'); await page.locator('.field.control.l-input-sm input').first().press('ArrowUp'); - const value = await page.locator('.field.control.l-input-sm input').first().inputValue(); - await expect(value).toBe('6'); + const value = page.locator('.field.control.l-input-sm input').first(); + await expect(value).toHaveValue('6'); - //Click text=OK - await Promise.all([page.waitForNavigation(), page.click('button:has-text("OK")')]); + //Click save button + await page.getByLabel('Save').click(); // Verify that the Sine Wave Generator is displayed and correct // Verify object properties diff --git a/e2e/tests/functional/forms.e2e.spec.js b/e2e/tests/functional/forms.e2e.spec.js index 7e3ba59221..761f20ea63 100644 --- a/e2e/tests/functional/forms.e2e.spec.js +++ b/e2e/tests/functional/forms.e2e.spec.js @@ -45,25 +45,23 @@ test.describe('Form Validation Behavior', () => { await page.getByRole('menuitem', { name: 'Folder' }).click(); // Fill in empty string into title and trigger validation with 'Tab' - await page.click('text=Properties Title Notes >> input[type="text"]'); - await page.fill('text=Properties Title Notes >> input[type="text"]', ''); - await page.press('text=Properties Title Notes >> input[type="text"]', 'Tab'); + await page.getByLabel('Title', { exact: true }).fill(''); + await page.getByLabel('Title', { exact: true }).press('Tab'); //Required Field Form Validation - await expect(page.locator('button:has-text("OK")')).toBeDisabled(); + await expect(page.getByLabel('Save')).toBeDisabled(); await expect(page.locator('.c-form-row__state-indicator').first()).toHaveClass(/invalid/); //Correct Form Validation for missing title and trigger validation with 'Tab' - await page.click('text=Properties Title Notes >> input[type="text"]'); - await page.fill('text=Properties Title Notes >> input[type="text"]', TEST_FOLDER); - await page.press('text=Properties Title Notes >> input[type="text"]', 'Tab'); + await page.getByLabel('Title', { exact: true }).fill(TEST_FOLDER); + await page.getByLabel('Title', { exact: true }).press('Tab'); //Required Field Form Validation is corrected - await expect(page.locator('button:has-text("OK")')).toBeEnabled(); + await expect(page.getByLabel('Save')).toBeEnabled(); await expect(page.locator('.c-form-row__state-indicator').first()).not.toHaveClass(/invalid/); //Finish Creating Domain Object - await Promise.all([page.waitForNavigation(), page.click('button:has-text("OK")')]); + await page.getByLabel('Save').click(); //Verify that the Domain Object has been created with the corrected title property await expect(page.locator('.l-browse-bar__object-name')).toContainText(TEST_FOLDER); @@ -87,8 +85,8 @@ test.describe('Form File Input Behavior', () => { await page.getByRole('button', { name: 'Save' }).click(); - const type = await page.locator('#file-input-type').textContent(); - await expect(type).toBe(`"string"`); + const type = page.locator('#file-input-type'); + await expect(type).toHaveText(`"string"`); }); test('Can select an image file type', async ({ page }) => { @@ -101,8 +99,8 @@ test.describe('Form File Input Behavior', () => { await page.getByRole('button', { name: 'Save' }).click(); - const type = await page.locator('#file-input-type').textContent(); - await expect(type).toBe(`"object"`); + const type = page.locator('#file-input-type'); + await expect(type).toHaveText(`"object"`); }); }); @@ -123,11 +121,11 @@ test.describe('Persistence operations @addInit', () => { await page.getByRole('button', { name: 'Create' }).click(); - await page.click('text=Condition Set'); + await page.getByRole('menuitem', { name: 'Condition Set' }).click(); await page.locator('form[name="mctForm"] >> text=Persistence Testing').click(); - const okButton = page.locator('button:has-text("OK")'); + const okButton = page.getByLabel('Save'); await expect(okButton).toBeDisabled(); }); }); @@ -158,14 +156,12 @@ test.describe('Persistence operations @couchdb', () => { }); // Open the edit form for the clock object - await page.click('button[title="More actions"]'); - await page.click('li[title="Edit properties of this object."]'); + await page.getByLabel('More actions').click(); + await page.getByLabel('Edit Properties...').click(); // Modify the display format from default 12hr -> 24hr and click 'Save' - await page - .locator('select[aria-label="12 or 24 hour clock"]') - .selectOption({ value: 'clock24' }); - await page.click('button[aria-label="Save"]'); + await page.getByLabel('12 or 24 hour clock').selectOption({ value: 'clock24' }); + await page.getByLabel('Save').click(); await expect .poll(() => putRequestCount, { @@ -188,8 +184,8 @@ test.describe('Persistence operations @couchdb', () => { // Both pages: Go to baseURL await Promise.all([ - page.goto('./', { waitUntil: 'networkidle' }), - page2.goto('./', { waitUntil: 'networkidle' }) + page.goto('./', { waitUntil: 'domcontentloaded' }), + page2.goto('./', { waitUntil: 'domcontentloaded' }) ]); //Slow down the test a bit @@ -202,14 +198,14 @@ test.describe('Persistence operations @couchdb', () => { // Both pages: Click the Create button await Promise.all([ - page.click('button:has-text("Create")'), - page2.click('button:has-text("Create")') + page.getByRole('button', { name: 'Create' }).click(), + page2.getByRole('button', { name: 'Create' }).click() ]); // Both pages: Click "Clock" in the Create menu await Promise.all([ - page.click(`li[role='menuitem']:text("Clock")`), - page2.click(`li[role='menuitem']:text("Clock")`) + page.getByRole('menuitem', { name: 'Clock' }).click(), + page2.getByRole('menuitem', { name: 'Clock' }).click() ]); // Generate unique names for both objects @@ -236,9 +232,9 @@ test.describe('Persistence operations @couchdb', () => { // conditions for a conflict error from the first page. await Promise.all([ page2.waitForLoadState(), - page2.click('[aria-label="Save"]'), + page2.getByLabel('Save').click(), // Wait for Save Banner to appear - page2.waitForSelector('.c-message-banner__message') + page2.locator('.c-message-banner__message').hover({ trial: true }) ]); // Close Page 2, we're done with it. @@ -249,9 +245,9 @@ test.describe('Persistence operations @couchdb', () => { // the composition of the parent folder. await Promise.all([ page.waitForLoadState(), - page.click('[aria-label="Save"]'), + page.getByLabel('Save').click(), // Wait for Save Banner to appear - page.waitForSelector('.c-message-banner__message') + page.locator('.c-message-banner__message').hover({ trial: true }) ]); // Page 1: Verify that the conflict has occurred and an error notification is displayed. diff --git a/e2e/tests/functional/notification.e2e.spec.js b/e2e/tests/functional/notification.e2e.spec.js index 02984225dc..daee85aaf5 100644 --- a/e2e/tests/functional/notification.e2e.spec.js +++ b/e2e/tests/functional/notification.e2e.spec.js @@ -50,13 +50,13 @@ test.describe('Notifications List', () => { }); // Verify that there is a button with aria-label "Review 2 Notifications" - expect(await page.locator('button[aria-label="Review 2 Notifications"]').count()).toBe(1); + await expect(page.locator('button[aria-label="Review 2 Notifications"]')).toHaveCount(1); // Click on button with aria-label "Review 2 Notifications" - await page.click('button[aria-label="Review 2 Notifications"]'); + await page.getByLabel('Review 2 Notifications').click(); // Click on button with aria-label="Dismiss notification of Error message" - await page.click('button[aria-label="Dismiss notification of Error message"]'); + await page.getByLabel('Dismiss notification of Error message').click(); // Verify there is no a notification (listitem) with the text "Error message" since it was dismissed expect(await page.locator('div[role="dialog"] div[role="listitem"]').innerText()).not.toContain( @@ -69,10 +69,10 @@ test.describe('Notifications List', () => { ); // Click on button with aria-label="Dismiss notification of Alert message" - await page.click('button[aria-label="Dismiss notification of Alert message"]'); + await page.getByLabel('Dismiss notification of Alert message').click(); // Verify that there is no dialog since the notification overlay was closed automatically after all notifications were dismissed - expect(await page.locator('div[role="dialog"]').count()).toBe(0); + await expect(page.locator('div[role="dialog"]')).toHaveCount(0); }); }); diff --git a/e2e/tests/functional/planning/plan.e2e.spec.js b/e2e/tests/functional/planning/plan.e2e.spec.js index 61784bfa9a..a34fe23732 100644 --- a/e2e/tests/functional/planning/plan.e2e.spec.js +++ b/e2e/tests/functional/planning/plan.e2e.spec.js @@ -21,7 +21,7 @@ *****************************************************************************/ import fs from 'fs'; -import { createPlanFromJSON } from '../../../appActions.js'; +import { createPlanFromJSON, navigateToObjectWithFixedTimeBounds } from '../../../appActions.js'; import { addPlanGetInterceptor, assertPlanActivities, @@ -81,9 +81,7 @@ test.describe('Plan', () => { } // Switch to fixed time mode with all plan events within the bounds - await page.goto( - `${plan.url}?tc.mode=fixed&tc.startBound=${startBound}&tc.endBound=${endBound}&tc.timeSystem=utc&view=plan.view` - ); + await navigateToObjectWithFixedTimeBounds(page, plan.url, startBound, endBound); // select the first activity in the list await page.getByText('Past event 1').click(); diff --git a/e2e/tests/functional/planning/timelist.e2e.spec.js b/e2e/tests/functional/planning/timelist.e2e.spec.js index a0e600d63f..abfef4a584 100644 --- a/e2e/tests/functional/planning/timelist.e2e.spec.js +++ b/e2e/tests/functional/planning/timelist.e2e.spec.js @@ -21,7 +21,11 @@ *****************************************************************************/ import fs from 'fs'; -import { createDomainObjectWithDefaults, createPlanFromJSON } from '../../../appActions.js'; +import { + createDomainObjectWithDefaults, + createPlanFromJSON, + navigateToObjectWithFixedTimeBounds +} from '../../../appActions.js'; import { expect, test } from '../../../pluginFixtures.js'; const examplePlanSmall1 = JSON.parse( @@ -59,14 +63,12 @@ test.describe('Time List', () => { const endBound = lastActivity.end; // Switch to fixed time mode with all plan events within the bounds - await page.goto( - `${timelist.url}?tc.mode=fixed&tc.startBound=${startBound}&tc.endBound=${endBound}&tc.timeSystem=utc&view=timelist.view` - ); + await navigateToObjectWithFixedTimeBounds(page, timelist.url, startBound, endBound); // Verify all events are displayed const eventCount = await page.getByRole('row').count(); // subtracting one for the header - await expect(eventCount - 1).toEqual(firstGroupItems.length); + expect(eventCount - 1).toEqual(firstGroupItems.length); }); await test.step('Does not show milliseconds in times', async () => { @@ -124,9 +126,7 @@ test("View a timelist in expanded view, verify all the activities are displayed const endBound = lastActivity.end; // Switch to fixed time mode with all plan events within the bounds - await page.goto( - `${timelist.url}?tc.mode=fixed&tc.startBound=${startBound}&tc.endBound=${endBound}&tc.timeSystem=utc&view=timelist.view` - ); + await navigateToObjectWithFixedTimeBounds(page, timelist.url, startBound, endBound); // Change the object to edit mode await page.getByRole('button', { name: 'Edit Object' }).click(); @@ -142,7 +142,7 @@ test("View a timelist in expanded view, verify all the activities are displayed // Verify all events are displayed const eventCount = await page.getByRole('row').count(); - await expect(eventCount).toEqual(firstGroupItems.length); + expect(eventCount).toEqual(firstGroupItems.length); }); await test.step('Shows activity properties when a row is selected in the expanded view', async () => { @@ -158,7 +158,7 @@ test("View a timelist in expanded view, verify all the activities are displayed await test.step("Verify absence of progress indication for an activity that's not in progress", async () => { // When an activity is not in progress, the progress pie is not visible - const hidden = await page.getByRole('row').locator('path').nth(1).isHidden(); - await expect(hidden).toBe(true); + const hidden = page.getByRole('row').locator('path').nth(1); + await expect(hidden).toBeHidden(); }); }); diff --git a/e2e/tests/functional/planning/timelistControlledClock.e2e.spec.js b/e2e/tests/functional/planning/timelistControlledClock.e2e.spec.js index 4baec66718..156d48aebe 100644 --- a/e2e/tests/functional/planning/timelistControlledClock.e2e.spec.js +++ b/e2e/tests/functional/planning/timelistControlledClock.e2e.spec.js @@ -22,12 +22,16 @@ /* Collection of Time List tests set to run with browser clock manipulate made possible with the -clockOptions plugin fixture. +page.clock() API. */ import fs from 'fs'; -import { createDomainObjectWithDefaults, createPlanFromJSON } from '../../../appActions.js'; +import { + createDomainObjectWithDefaults, + createPlanFromJSON, + navigateToObjectWithRealTime +} from '../../../appActions.js'; import { createTimelistWithPlanAndSetActivityInProgress, getEarliestStartTime, @@ -93,14 +97,12 @@ const COUNTDOWN = Object.freeze({ SECONDS: 5 }); +const FIRST_ACTIVITY_SMALL_1 = getFirstActivity(examplePlanSmall1); + test.describe('Time List with controlled clock @clock', () => { - test.use({ - clockOptions: { - now: getEarliestStartTime(examplePlanSmall3), - shouldAdvanceTime: true - } - }); test.beforeEach(async ({ page }) => { + await page.clock.install({ time: getEarliestStartTime(examplePlanSmall3) }); + await page.clock.resume(); await page.goto('./', { waitUntil: 'domcontentloaded' }); // Create Time List const timelist = await createDomainObjectWithDefaults(page, { @@ -115,9 +117,7 @@ test.describe('Time List with controlled clock @clock', () => { }); // Navigate to the Time List in real-time mode - await page.goto( - `${timelist.url}?tc.mode=local&tc.startDelta=900000&tc.endDelta=1800000&tc.timeSystem=utc&view=grid` - ); + await navigateToObjectWithRealTime(page, timelist.url, 900000, 1800000); //Expand the viewport to show the entire time list await page.getByLabel('Collapse Inspect Pane').click(); @@ -172,16 +172,9 @@ test.describe('Time List with controlled clock @clock', () => { }); test.describe('Activity progress when activity is in the future @clock', () => { - const firstActivity = getFirstActivity(examplePlanSmall1); - - test.use({ - clockOptions: { - now: firstActivity.start - 1, - shouldAdvanceTime: true - } - }); - test.beforeEach(async ({ page }) => { + await page.clock.install({ time: FIRST_ACTIVITY_SMALL_1.start - 1 }); + await page.clock.resume(); await createTimelistWithPlanAndSetActivityInProgress(page, examplePlanSmall1); }); @@ -195,18 +188,13 @@ test.describe('Activity progress when activity is in the future @clock', () => { }); test.describe('Activity progress when now is between start and end of the activity @clock', () => { - const firstActivity = getFirstActivity(examplePlanSmall1); test.beforeEach(async ({ page }) => { + await page.clock.install({ time: FIRST_ACTIVITY_SMALL_1.start + 50000 }); + await page.clock.resume(); + await page.goto('./', { waitUntil: 'domcontentloaded' }); await createTimelistWithPlanAndSetActivityInProgress(page, examplePlanSmall1); }); - test.use({ - clockOptions: { - now: firstActivity.start + 50000, - shouldAdvanceTime: true - } - }); - test('progress pie is partially filled', async ({ page }) => { const anActivity = page.getByRole('row').nth(0); const pathElement = anActivity.getByLabel('Activity in progress').locator('path'); @@ -216,16 +204,10 @@ test.describe('Activity progress when now is between start and end of the activi }); test.describe('Activity progress when now is after end of the activity @clock', () => { - const firstActivity = getFirstActivity(examplePlanSmall1); - - test.use({ - clockOptions: { - now: firstActivity.end + 10000, - shouldAdvanceTime: true - } - }); - test.beforeEach(async ({ page }) => { + await page.clock.install({ time: FIRST_ACTIVITY_SMALL_1.end + 10000 }); + await page.clock.resume(); + await page.goto('./', { waitUntil: 'domcontentloaded' }); await createTimelistWithPlanAndSetActivityInProgress(page, examplePlanSmall1); }); diff --git a/e2e/tests/functional/planning/timestrip.e2e.spec.js b/e2e/tests/functional/planning/timestrip.e2e.spec.js index e6b36181c7..1d82071cd1 100644 --- a/e2e/tests/functional/planning/timestrip.e2e.spec.js +++ b/e2e/tests/functional/planning/timestrip.e2e.spec.js @@ -23,7 +23,8 @@ import { createDomainObjectWithDefaults, createPlanFromJSON, - setIndependentTimeConductorBounds + navigateToObjectWithFixedTimeBounds, + setFixedIndependentTimeConductorBounds } from '../../../appActions.js'; import { expect, test } from '../../../pluginFixtures.js'; @@ -73,7 +74,7 @@ const testPlan = { }; test.describe('Time Strip', () => { - test('Create two Time Strips, add a single Plan to both, and verify they can have separate Independent Time Contexts @unstable', async ({ + test('Create two Time Strips, add a single Plan to both, and verify they can have separate Independent Time Contexts', async ({ page }) => { test.info().annotations.push({ @@ -103,17 +104,17 @@ test.describe('Time Strip', () => { await page.goto(timestrip.url); // Expand the tree to show the plan - await page.click("button[title='Show selected item in tree']"); - await page.dragAndDrop(`role=treeitem[name=/${createdPlan.name}/]`, '.c-object-view'); - await page.click("button[title='Save']"); - await page.click("li[title='Save and Finish Editing']"); + await page.getByLabel('Show selected item in tree').click(); + await page + .getByLabel(`Navigate to ${createdPlan.name}`) + .dragTo(page.getByLabel('Object View')); + await page.getByLabel('Save').click(); + await page.getByRole('listitem', { name: 'Save and Finish Editing' }).click(); const startBound = testPlan.TEST_GROUP[0].start; const endBound = testPlan.TEST_GROUP[testPlan.TEST_GROUP.length - 1].end; // Switch to fixed time mode with all plan events within the bounds - await page.goto( - `${timestrip.url}?tc.mode=fixed&tc.startBound=${startBound}&tc.endBound=${endBound}&tc.timeSystem=utc&view=time-strip.view` - ); + await navigateToObjectWithFixedTimeBounds(page, timestrip.url, startBound, endBound); // Verify all events are displayed const eventCount = await page.locator('.activity-bounds').count(); @@ -131,7 +132,7 @@ test.describe('Time Strip', () => { const startBoundString = new Date(startBound).toISOString().replace('T', ' '); const endBoundString = new Date(endBound).toISOString().replace('T', ' '); - await setIndependentTimeConductorBounds(page, { + await setFixedIndependentTimeConductorBounds(page, { start: startBoundString, end: endBoundString }); @@ -149,9 +150,9 @@ test.describe('Time Strip', () => { expect(objectName).toBe(createdTimeStrip.name); // Drag the existing Plan onto the newly created Time Strip, and save. - await page.dragAndDrop(`role=treeitem[name=/${plan.name}/]`, '.c-object-view'); - await page.click("button[title='Save']"); - await page.click("li[title='Save and Finish Editing']"); + await page.getByLabel(`Navigate to ${plan.name}`).dragTo(page.getByLabel('Object View')); + await page.getByLabel('Save').click(); + await page.getByRole('listitem', { name: 'Save and Finish Editing' }).click(); // All events should be displayed at this point because the // initial independent context bounds will match the global bounds @@ -163,7 +164,7 @@ test.describe('Time Strip', () => { const startBoundString = new Date(startBound).toISOString().replace('T', ' '); const endBoundString = new Date(endBound).toISOString().replace('T', ' '); - await setIndependentTimeConductorBounds(page, { + await setFixedIndependentTimeConductorBounds(page, { start: startBoundString, end: endBoundString }); diff --git a/e2e/tests/functional/plugins/conditionSet/conditionSet.e2e.spec.js b/e2e/tests/functional/plugins/conditionSet/conditionSet.e2e.spec.js index bfa1e6e015..6b76088b39 100644 --- a/e2e/tests/functional/plugins/conditionSet/conditionSet.e2e.spec.js +++ b/e2e/tests/functional/plugins/conditionSet/conditionSet.e2e.spec.js @@ -41,11 +41,10 @@ test.describe.serial('Condition Set CRUD Operations on @localStorage @2p', () => const context = await browser.newContext(); const page = await context.newPage(); await page.goto('./', { waitUntil: 'domcontentloaded' }); - await page.getByRole('button', { name: 'Create' }).click(); - - await page.locator('li[role="menuitem"]:has-text("Condition Set")').click(); - - await Promise.all([page.waitForNavigation(), page.click('button:has-text("OK")')]); + const conditionSet = await createDomainObjectWithDefaults(page, { + type: 'Condition Set', + name: 'Unnamed Condition Set' + }); //Save localStorage for future test execution await context.storageState({ @@ -55,7 +54,7 @@ test.describe.serial('Condition Set CRUD Operations on @localStorage @2p', () => }); //Set object identifier from url - conditionSetUrl = page.url(); + conditionSetUrl = conditionSet.url; await page.close(); }); @@ -68,44 +67,39 @@ test.describe.serial('Condition Set CRUD Operations on @localStorage @2p', () => }); //Begin suite of tests again localStorage - test.fixme( - 'Condition set object properties persist in main view and inspector @localStorage', - async ({ page }) => { - test.info().annotations.push({ - type: 'issue', - description: 'https://github.com/nasa/openmct/issues/7421' - }); - //Navigate to baseURL with injected localStorage - await page.goto(conditionSetUrl, { waitUntil: 'networkidle' }); + test('Condition set object properties persist in main view and inspector after reload @localStorage', async ({ + page + }) => { + //Navigate to baseURL with injected localStorage + await page.goto(conditionSetUrl, { waitUntil: 'domcontentloaded' }); - //Assertions on loaded Condition Set in main view. This is a stateful transition step after page.goto() - await expect - .soft(page.locator('.l-browse-bar__object-name')) - .toContainText('Unnamed Condition Set'); + //Assertions on loaded Condition Set in main view. This is a stateful transition step after page.goto() + await expect.soft(page.getByRole('main')).toContainText('Unnamed Condition Set'); - //Assertions on loaded Condition Set in Inspector - expect.soft(page.locator('_vue=item.name=Unnamed Condition Set')).toBeTruthy(); + //Assertions on loaded Condition Set in Inspector + await expect( + page.getByLabel('Title inspector properties').getByLabel('inspector property value') + ).toContainText('Unnamed Condition Set'); - //Reload Page - await Promise.all([page.reload(), page.waitForLoadState('networkidle')]); + //Reload Page + await page.reload({ waitUntil: 'domcontentloaded' }); + + //Re-verify after reload + await expect(page.getByRole('main')).toContainText('Unnamed Condition Set'); + + //Assertions on loaded Condition Set in Inspector + await expect( + page.getByLabel('Title inspector properties').getByLabel('inspector property value') + ).toContainText('Unnamed Condition Set'); + }); - //Re-verify after reload - await expect - .soft(page.locator('.l-browse-bar__object-name')) - .toContainText('Unnamed Condition Set'); - //Assertions on loaded Condition Set in Inspector - expect.soft(page.locator('_vue=item.name=Unnamed Condition Set')).toBeTruthy(); - } - ); test('condition set object can be modified on @localStorage', async ({ page, openmctConfig }) => { const { myItemsFolderName } = openmctConfig; - await page.goto(conditionSetUrl, { waitUntil: 'networkidle' }); + await page.goto(conditionSetUrl, { waitUntil: 'domcontentloaded' }); //Assertions on loaded Condition Set in main view. This is a stateful transition step after page.goto() - await expect - .soft(page.locator('.l-browse-bar__object-name')) - .toContainText('Unnamed Condition Set'); + await expect(page.locator('.l-browse-bar__object-name')).toContainText('Unnamed Condition Set'); //Update the Condition Set properties // Click Edit Button @@ -151,7 +145,7 @@ test.describe.serial('Condition Set CRUD Operations on @localStorage @2p', () => expect(page.locator('a:has-text("Renamed Condition Set")')).toBeTruthy(); //Reload Page - await Promise.all([page.reload(), page.waitForLoadState('networkidle')]); + await Promise.all([page.reload(), page.waitForLoadState('domcontentloaded')]); //Verify Main section reflects updated Name Property await expect @@ -213,7 +207,7 @@ test.describe.serial('Condition Set CRUD Operations on @localStorage @2p', () => //Feature? //Domain Object is still available by direct URL after delete - await page.goto(conditionSetUrl, { waitUntil: 'networkidle' }); + await page.goto(conditionSetUrl, { waitUntil: 'domcontentloaded' }); await expect(page.locator('.l-browse-bar__object-name')).toContainText('Unnamed Condition Set'); }); }); @@ -267,7 +261,7 @@ test.describe('Basic Condition Set Use', () => { await page.getByLabel('Edit Object').click(); // Expand the 'My Items' folder in the left tree - page.click('button[title="Show selected item in tree"]'); + await page.getByLabel('Show selected item in tree').click(); // Add the Alpha & Beta Sine Wave Generator to the Condition Set and save changes const treePane = page.getByRole('tree', { name: 'Main Tree' @@ -538,7 +532,7 @@ test.describe('Condition Set Composition', () => { .getByLabel(`${exampleTelemetry.name} Context Menu`) .getByRole('menuitem', { name: 'Remove' }) .click(); - await page.getByRole('button', { name: 'OK', exact: true }).click(); + await page.getByRole('button', { name: 'Ok', exact: true }).click(); await page .getByLabel(`Navigate to ${conditionSet.name} conditionSet Object`, { exact: true }) diff --git a/e2e/tests/functional/plugins/conditionSet/conditionSetOperations.e2e.spec.js b/e2e/tests/functional/plugins/conditionSet/conditionSetOperations.e2e.spec.js new file mode 100644 index 0000000000..5cd5d86d25 --- /dev/null +++ b/e2e/tests/functional/plugins/conditionSet/conditionSetOperations.e2e.spec.js @@ -0,0 +1,368 @@ +/***************************************************************************** + * 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 the basic operations surrounding conditionSets. Note: this +suite is sharing state between tests which is considered an anti-pattern. Implementing in this way to +demonstrate some playwright for test developers. This pattern should not be re-used in other CRUD suites. +*/ + +import { + createDomainObjectWithDefaults, + createExampleTelemetryObject +} from '../../../../appActions.js'; +import { expect, test } from '../../../../pluginFixtures.js'; + +test.describe('Basic Condition Set Use', () => { + let conditionSet; + + test.beforeEach(async ({ page }) => { + // Open a browser, navigate to the main page, and wait until all network events to resolve + await page.goto('./', { waitUntil: 'domcontentloaded' }); + // Create a new condition set + conditionSet = await createDomainObjectWithDefaults(page, { + type: 'Condition Set', + name: 'Test Condition Set' + }); + }); + test('Creating a condition defaults the condition name to "Unnamed Condition"', async ({ + page + }) => { + await page.goto(conditionSet.url); + + // Change the object to edit mode + await page.getByLabel('Edit Object').click(); + + // Click Add Condition button + await page.locator('#addCondition').click(); + // Check that the new Unnamed Condition section appears + const numOfUnnamedConditions = await page + .locator('.c-condition__name', { hasText: 'Unnamed Condition' }) + .count(); + expect(numOfUnnamedConditions).toEqual(1); + }); + test('ConditionSet should display appropriate view options', async ({ page }) => { + test.info().annotations.push({ + type: 'issue', + description: 'https://github.com/nasa/openmct/issues/5924' + }); + + await createDomainObjectWithDefaults(page, { + type: 'Sine Wave Generator', + name: 'Alpha Sine Wave Generator' + }); + await createDomainObjectWithDefaults(page, { + type: 'Sine Wave Generator', + name: 'Beta Sine Wave Generator' + }); + + await page.goto(conditionSet.url); + + // Change the object to edit mode + await page.getByLabel('Edit Object').click(); + + // Expand the 'My Items' folder in the left tree + await page.getByLabel('Show selected item in tree').click(); + // Add the Alpha & Beta Sine Wave Generator to the Condition Set and save changes + const treePane = page.getByRole('tree', { + name: 'Main Tree' + }); + const alphaGeneratorTreeItem = treePane.getByRole('treeitem', { + name: 'Alpha Sine Wave Generator' + }); + const betaGeneratorTreeItem = treePane.getByRole('treeitem', { + name: 'Beta Sine Wave Generator' + }); + const conditionCollection = page.locator('#conditionCollection'); + + await alphaGeneratorTreeItem.dragTo(conditionCollection); + await betaGeneratorTreeItem.dragTo(conditionCollection); + + await page.locator('button[title="Save"]').click(); + await page.getByRole('listitem', { name: 'Save and Finish Editing' }).click(); + + await page.getByLabel('Open the View Switcher Menu').click(); + + await expect(page.getByRole('menuitem', { name: /Lad Table/ })).toBeHidden(); + await expect(page.getByRole('menuitem', { name: /Conditions View/ })).toBeVisible(); + await expect(page.getByRole('menuitem', { name: /Plot/ })).toBeVisible(); + await expect(page.getByRole('menuitem', { name: /Telemetry Table/ })).toBeVisible(); + await page.getByLabel('Plot').click(); + await expect( + page.getByLabel('Plot Legend Collapsed').getByText('Test Condition Set') + ).toBeVisible(); + await page.getByLabel('Open the View Switcher Menu').click(); + await page.getByLabel('Telemetry Table').click(); + await expect(page.getByRole('searchbox', { name: 'output filter input' })).toBeVisible(); + await page.getByLabel('Open the View Switcher Menu').click(); + await page.getByLabel('Conditions View').click(); + await expect(page.getByText('Current Output')).toBeVisible(); + }); + test('ConditionSet has correct outputs when telemetry is and is not available', async ({ + page + }) => { + const exampleTelemetry = await createExampleTelemetryObject(page); + + await page.getByLabel('Show selected item in tree').click(); + await page.goto(conditionSet.url); + // Change the object to edit mode + await page.getByLabel('Edit Object').click(); + + // Create two conditions + await page.locator('#addCondition').click(); + await page.locator('#addCondition').click(); + await page.locator('#conditionCollection').getByRole('textbox').nth(0).fill('First Condition'); + await page.locator('#conditionCollection').getByRole('textbox').nth(1).fill('Second Condition'); + + // Add Telemetry to ConditionSet + const sineWaveGeneratorTreeItem = page + .getByRole('tree', { + name: 'Main Tree' + }) + .getByRole('treeitem', { + name: exampleTelemetry.name + }); + const conditionCollection = page.locator('#conditionCollection'); + await sineWaveGeneratorTreeItem.dragTo(conditionCollection); + + // Modify First Criterion + const firstCriterionTelemetry = page.locator( + '[aria-label="Criterion Telemetry Selection"] >> nth=0' + ); + firstCriterionTelemetry.selectOption({ label: exampleTelemetry.name }); + const firstCriterionMetadata = page.locator( + '[aria-label="Criterion Metadata Selection"] >> nth=0' + ); + firstCriterionMetadata.selectOption({ label: 'Sine' }); + const firstCriterionComparison = page.locator( + '[aria-label="Criterion Comparison Selection"] >> nth=0' + ); + firstCriterionComparison.selectOption({ label: 'is greater than or equal to' }); + const firstCriterionInput = page.locator('[aria-label="Criterion Input"] >> nth=0'); + await firstCriterionInput.fill('0'); + + // Modify First Criterion + const secondCriterionTelemetry = page.locator( + '[aria-label="Criterion Telemetry Selection"] >> nth=1' + ); + secondCriterionTelemetry.selectOption({ label: exampleTelemetry.name }); + + const secondCriterionMetadata = page.locator( + '[aria-label="Criterion Metadata Selection"] >> nth=1' + ); + secondCriterionMetadata.selectOption({ label: 'Sine' }); + + const secondCriterionComparison = page.locator( + '[aria-label="Criterion Comparison Selection"] >> nth=1' + ); + secondCriterionComparison.selectOption({ label: 'is less than' }); + + const secondCriterionInput = page.locator('[aria-label="Criterion Input"] >> nth=1'); + await secondCriterionInput.fill('0'); + + // Save ConditionSet + await page.locator('button[title="Save"]').click(); + await page.getByRole('listitem', { name: 'Save and Finish Editing' }).click(); + + // Validate that the condition set is evaluating and outputting + // the correct value when the underlying telemetry subscription is active. + let outputValue = page.getByLabel('Current Output Value'); + await expect(outputValue).toHaveText('false'); + + await page.goto(exampleTelemetry.url); + + // Edit SWG to add 8 second loading delay to simulate the case + // where telemetry is not available. + await page.getByTitle('More actions').click(); + await page.getByRole('menuitem', { name: 'Edit Properties...' }).click(); + await page.getByRole('spinbutton', { name: 'Loading Delay (ms)' }).fill('8000'); + await page.getByLabel('Save').click(); + + // Expect that the output value is blank or '---' if the + // underlying telemetry subscription is not active. + await page.goto(conditionSet.url); + await expect(outputValue).toHaveText('---'); + }); + + test('ConditionSet has correct outputs when test data is enabled', async ({ page }) => { + const exampleTelemetry = await createExampleTelemetryObject(page); + + await page.getByLabel('Show selected item in tree').click(); + await page.goto(conditionSet.url); + // Change the object to edit mode + await page.getByLabel('Edit Object').click(); + + // Create two conditions + await page.locator('#addCondition').click(); + await page.locator('#addCondition').click(); + await page.locator('#conditionCollection').getByRole('textbox').nth(0).fill('First Condition'); + await page.locator('#conditionCollection').getByRole('textbox').nth(1).fill('Second Condition'); + + // Add Telemetry to ConditionSet + const sineWaveGeneratorTreeItem = page + .getByRole('tree', { + name: 'Main Tree' + }) + .getByRole('treeitem', { + name: exampleTelemetry.name + }); + const conditionCollection = page.locator('#conditionCollection'); + await sineWaveGeneratorTreeItem.dragTo(conditionCollection); + + // Modify First Criterion + const firstCriterionTelemetry = page.locator( + '[aria-label="Criterion Telemetry Selection"] >> nth=0' + ); + firstCriterionTelemetry.selectOption({ label: exampleTelemetry.name }); + const firstCriterionMetadata = page.locator( + '[aria-label="Criterion Metadata Selection"] >> nth=0' + ); + firstCriterionMetadata.selectOption({ label: 'Sine' }); + const firstCriterionComparison = page.locator( + '[aria-label="Criterion Comparison Selection"] >> nth=0' + ); + firstCriterionComparison.selectOption({ label: 'is greater than or equal to' }); + const firstCriterionInput = page.locator('[aria-label="Criterion Input"] >> nth=0'); + await firstCriterionInput.fill('0'); + + // Modify Second Criterion + const secondCriterionTelemetry = page.locator( + '[aria-label="Criterion Telemetry Selection"] >> nth=1' + ); + await secondCriterionTelemetry.selectOption({ label: exampleTelemetry.name }); + + const secondCriterionMetadata = page.locator( + '[aria-label="Criterion Metadata Selection"] >> nth=1' + ); + await secondCriterionMetadata.selectOption({ label: 'Sine' }); + + const secondCriterionComparison = page.locator( + '[aria-label="Criterion Comparison Selection"] >> nth=1' + ); + await secondCriterionComparison.selectOption({ label: 'is less than' }); + + const secondCriterionInput = page.locator('[aria-label="Criterion Input"] >> nth=1'); + await secondCriterionInput.fill('0'); + + // Enable test data + await page.getByLabel('Apply Test Data').nth(1).click(); + const testDataTelemetry = page.locator('[aria-label="Test Data Telemetry Selection"] >> nth=0'); + await testDataTelemetry.selectOption({ label: exampleTelemetry.name }); + + const testDataMetadata = page.locator('[aria-label="Test Data Metadata Selection"] >> nth=0'); + await testDataMetadata.selectOption({ label: 'Sine' }); + + const testInput = page.locator('[aria-label="Test Data Input"] >> nth=0'); + await testInput.fill('0'); + + // Validate that the condition set is evaluating and outputting + // the correct value when the underlying telemetry subscription is active. + let outputValue = page.getByLabel('Current Output Value'); + await expect(outputValue).toHaveText('false'); + + await page.goto(exampleTelemetry.url); + }); + + test.fixme('Ensure condition sets work with telemetry like operator status', ({ page }) => { + test.info().annotations.push({ + type: 'issue', + description: 'https://github.com/nasa/openmct/issues/7484' + }); + }); +}); + +test.describe('Condition Set Composition', () => { + let conditionSet; + let exampleTelemetry; + + test.beforeEach(async ({ page }) => { + await page.goto('./', { waitUntil: 'domcontentloaded' }); + + // Create Condition Set + conditionSet = await createDomainObjectWithDefaults(page, { + type: 'Condition Set' + }); + + // Create Telemetry Object as child to Condition Set + exampleTelemetry = await createExampleTelemetryObject(page, conditionSet.uuid); + + // Edit Condition Set + await page.goto(conditionSet.url); + await page.getByRole('button', { name: 'Edit Object' }).click(); + + // Add Condition to Condition Set + await page.getByRole('button', { name: 'Add Condition' }).click(); + + // Enter Condition Output + await page.getByLabel('Condition Name Input').first().fill('Negative'); + await page.getByLabel('Condition Output Type').first().selectOption({ value: 'string' }); + await page.getByLabel('Condition Output String').first().fill('Negative'); + + // Condition Trigger default is okay so no change needed to form + + // Enter Condition Criterion + await page.getByLabel('Criterion Telemetry Selection').first().selectOption({ value: 'all' }); + await page.getByLabel('Criterion Metadata Selection').first().selectOption({ value: 'sin' }); + await page + .locator('select[aria-label="Criterion Comparison Selection"]') + .first() + .selectOption({ value: 'lessThan' }); + await page.getByLabel('Criterion Input').first().fill('0'); + + // Save the Condition Set + await page.getByRole('button', { name: 'Save' }).click(); + await page.getByRole('listitem', { name: 'Save and Finish Editing' }).click(); + }); + + test('You can remove telemetry from a condition set with existing conditions', async ({ + page + }) => { + test.info().annotations.push({ + type: 'issue', + description: 'https://github.com/nasa/openmct/issues/7710' + }); + + await page.getByLabel('Expand My Items folder').click(); + await page.getByLabel(`Expand ${conditionSet.name} conditionSet`).click(); + + await page + .getByLabel(`Navigate to ${exampleTelemetry.name}`, { exact: false }) + .click({ button: 'right' }); + + await page + .getByLabel(`${exampleTelemetry.name} Context Menu`) + .getByRole('menuitem', { name: 'Remove' }) + .click(); + await page.getByRole('button', { name: 'Ok', exact: true }).click(); + + await page + .getByLabel(`Navigate to ${conditionSet.name} conditionSet Object`, { exact: true }) + .click(); + await page.getByRole('button', { name: 'Edit Object' }).click(); + await page.getByRole('tab', { name: 'Elements' }).click(); + expect( + await page + .getByRole('tabpanel', { name: 'Inspector Views' }) + .getByRole('listitem', { name: exampleTelemetry.name }) + .count() + ).toEqual(0); + }); +}); diff --git a/e2e/tests/functional/plugins/displayLayout/displayLayout.e2e.spec.js b/e2e/tests/functional/plugins/displayLayout/displayLayout.e2e.spec.js index a187c88522..5b69dc6b90 100644 --- a/e2e/tests/functional/plugins/displayLayout/displayLayout.e2e.spec.js +++ b/e2e/tests/functional/plugins/displayLayout/displayLayout.e2e.spec.js @@ -24,8 +24,8 @@ import { fileURLToPath } from 'url'; import { createDomainObjectWithDefaults, navigateToObjectWithFixedTimeBounds, + setFixedIndependentTimeConductorBounds, setFixedTimeMode, - setIndependentTimeConductorBounds, setRealTimeMode, setStartOffset } from '../../../../appActions.js'; @@ -75,18 +75,12 @@ test.describe('Display Layout Sub-object Actions @localStorage', () => { const TEST_FIXED_END_TIME = TEST_FIXED_START_TIME + 3600000; // 2024-11-11 20:11:11.000Z // Verify the ITC has the expected initial bounds - expect( - await page - .getByLabel('Child Overlay Plot 1 Frame Controls') - .getByLabel('Start bounds') - .textContent() - ).toEqual(INIT_ITC_START_BOUNDS); - expect( - await page - .getByLabel('Child Overlay Plot 1 Frame Controls') - .getByLabel('End bounds') - .textContent() - ).toEqual(INIT_ITC_END_BOUNDS); + await expect( + page.getByLabel('Child Overlay Plot 1 Frame Controls').getByLabel('Start bounds') + ).toHaveText(INIT_ITC_START_BOUNDS); + await expect( + page.getByLabel('Child Overlay Plot 1 Frame Controls').getByLabel('End bounds') + ).toHaveText(INIT_ITC_END_BOUNDS); // Update the global fixed bounds to 2024-11-11 19:11:11.000Z / 2024-11-11 20:11:11.000Z const url = page.url().split('?')[0]; @@ -98,18 +92,12 @@ test.describe('Display Layout Sub-object Actions @localStorage', () => { ); // ITC bounds should still match the initial ITC bounds - expect( - await page - .getByLabel('Child Overlay Plot 1 Frame Controls') - .getByLabel('Start bounds') - .textContent() - ).toEqual(INIT_ITC_START_BOUNDS); - expect( - await page - .getByLabel('Child Overlay Plot 1 Frame Controls') - .getByLabel('End bounds') - .textContent() - ).toEqual(INIT_ITC_END_BOUNDS); + await expect( + page.getByLabel('Child Overlay Plot 1 Frame Controls').getByLabel('Start bounds') + ).toHaveText(INIT_ITC_START_BOUNDS); + await expect( + page.getByLabel('Child Overlay Plot 1 Frame Controls').getByLabel('End bounds') + ).toHaveText(INIT_ITC_END_BOUNDS); // Open the Child Overlay Plot 1 in a new tab await page.getByLabel('View menu items').click(); @@ -120,28 +108,22 @@ test.describe('Display Layout Sub-object Actions @localStorage', () => { await newPage.waitForLoadState('domcontentloaded'); // Verify that the global time conductor bounds in the new page match the updated global bounds - expect( - await newPage.getByLabel('Global Time Conductor').getByLabel('Start bounds').textContent() - ).toEqual(NEW_GLOBAL_START_BOUNDS); - expect( - await newPage.getByLabel('Global Time Conductor').getByLabel('End bounds').textContent() - ).toEqual(NEW_GLOBAL_END_BOUNDS); + await expect(newPage.getByLabel('Global Time Conductor').getByLabel('Start bounds')).toHaveText( + NEW_GLOBAL_START_BOUNDS + ); + await expect(newPage.getByLabel('Global Time Conductor').getByLabel('End bounds')).toHaveText( + NEW_GLOBAL_END_BOUNDS + ); // Verify that the ITC is enabled in the new page await expect(newPage.getByLabel('Disable Independent Time Conductor')).toBeVisible(); // Verify that the ITC bounds in the new page match the original ITC bounds - expect( - await newPage - .getByLabel('Independent Time Conductor Panel') - .getByLabel('Start bounds') - .textContent() - ).toEqual(INIT_ITC_START_BOUNDS); - expect( - await newPage - .getByLabel('Independent Time Conductor Panel') - .getByLabel('End bounds') - .textContent() - ).toEqual(INIT_ITC_END_BOUNDS); + await expect( + newPage.getByLabel('Independent Time Conductor Panel').getByLabel('Start bounds') + ).toHaveText(INIT_ITC_START_BOUNDS); + await expect( + newPage.getByLabel('Independent Time Conductor Panel').getByLabel('End bounds') + ).toHaveText(INIT_ITC_END_BOUNDS); }); }); @@ -174,17 +156,17 @@ test.describe('Display Layout Toolbar Actions @localStorage', () => { test('can add/remove Image to a single layout', async ({ page }) => { const layoutObject = 'Image'; await test.step("Add and remove image element from the parent's layout", async () => { - expect(await page.getByLabel(`Move ${layoutObject} Frame`).count()).toBe(0); + await expect(page.getByLabel(`Move ${layoutObject} Frame`)).toHaveCount(0); await addLayoutObject(page, PARENT_DISPLAY_LAYOUT_NAME, layoutObject); - expect(await page.getByLabel(`Move ${layoutObject} Frame`).count()).toBe(1); + await expect(page.getByLabel(`Move ${layoutObject} Frame`)).toHaveCount(1); await removeLayoutObject(page, layoutObject); - expect(await page.getByLabel(`Move ${layoutObject} Frame`).count()).toBe(0); + await expect(page.getByLabel(`Move ${layoutObject} Frame`)).toHaveCount(0); }); await test.step("Add and remove image from the child's layout", async () => { await addLayoutObject(page, CHILD_DISPLAY_LAYOUT_NAME1, layoutObject); - expect(await page.getByLabel(`Move ${layoutObject} Frame`).count()).toBe(1); + await expect(page.getByLabel(`Move ${layoutObject} Frame`)).toHaveCount(1); await removeLayoutObject(page, layoutObject); - expect(await page.getByLabel(`Move ${layoutObject} Frame`).count()).toBe(0); + await expect(page.getByLabel(`Move ${layoutObject} Frame`)).toHaveCount(0); }); }); test(`can add/remove Box to a single layout`, async ({ page }) => { @@ -253,20 +235,17 @@ test.describe('Display Layout', () => { const sineWaveGeneratorTreeItem = treePane.getByRole('treeitem', { name: new RegExp(sineWaveObject.name) }); - const layoutGridHolder = page.locator('.l-layout__grid-holder'); - await sineWaveGeneratorTreeItem.dragTo(layoutGridHolder); + await sineWaveGeneratorTreeItem.dragTo(page.getByLabel('Layout Grid')); await page.locator('button[title="Save"]').click(); await page.getByRole('listitem', { name: 'Save and Finish Editing' }).click(); // Subscribe to the Sine Wave Generator data // On getting data, check if the value found in the Display Layout is the most recent value // from the Sine Wave Generator - const getTelemValuePromise = await subscribeToTelemetry(page, sineWaveObject.uuid); - const formattedTelemetryValue = getTelemValuePromise; - const displayLayoutValuePromise = await page.waitForSelector( - `text="${formattedTelemetryValue}"` - ); - const displayLayoutValue = await displayLayoutValuePromise.textContent(); + const getTelemValuePromise = subscribeToTelemetry(page, sineWaveObject.uuid); + const formattedTelemetryValue = await getTelemValuePromise; + await expect(page.getByText(formattedTelemetryValue)).toBeVisible(); + const displayLayoutValue = await page.getByText(formattedTelemetryValue).textContent(); const trimmedDisplayValue = displayLayoutValue.trim(); expect(trimmedDisplayValue).toBe(formattedTelemetryValue); @@ -298,24 +277,21 @@ test.describe('Display Layout', () => { const sineWaveGeneratorTreeItem = treePane.getByRole('treeitem', { name: new RegExp(sineWaveObject.name) }); - const layoutGridHolder = page.locator('.l-layout__grid-holder'); - await sineWaveGeneratorTreeItem.dragTo(layoutGridHolder); + await sineWaveGeneratorTreeItem.dragTo(page.getByLabel('Layout Grid')); await page.locator('button[title="Save"]').click(); await page.getByRole('listitem', { name: 'Save and Finish Editing' }).click(); // Subscribe to the Sine Wave Generator data - const getTelemValuePromise = await subscribeToTelemetry(page, sineWaveObject.uuid); + const getTelemValuePromise = subscribeToTelemetry(page, sineWaveObject.uuid); // Set an offset of 1 minute and then change the time mode to fixed to set a 1 minute historical window - await setStartOffset(page, { mins: '1' }); + await setStartOffset(page, { startMins: '1' }); await setFixedTimeMode(page); // On getting data, check if the value found in the Display Layout is the most recent value // from the Sine Wave Generator - const formattedTelemetryValue = getTelemValuePromise; - const displayLayoutValuePromise = await page.waitForSelector( - `text="${formattedTelemetryValue}"` - ); - const displayLayoutValue = await displayLayoutValuePromise.textContent(); + const formattedTelemetryValue = await getTelemValuePromise; + await expect(page.getByText(formattedTelemetryValue)).toBeVisible(); + const displayLayoutValue = await page.getByText(formattedTelemetryValue).textContent(); const trimmedDisplayValue = displayLayoutValue.trim(); expect(trimmedDisplayValue).toBe(formattedTelemetryValue); @@ -340,8 +316,7 @@ test.describe('Display Layout', () => { const sineWaveGeneratorTreeItem = treePane.getByRole('treeitem', { name: new RegExp(sineWaveObject.name) }); - const layoutGridHolder = page.locator('.l-layout__grid-holder'); - await sineWaveGeneratorTreeItem.dragTo(layoutGridHolder); + await sineWaveGeneratorTreeItem.dragTo(page.getByLabel('Layout Grid')); await page.locator('button[title="Save"]').click(); await page.getByRole('listitem', { name: 'Save and Finish Editing' }).click(); @@ -382,8 +357,7 @@ test.describe('Display Layout', () => { const sineWaveGeneratorTreeItem = treePane.getByRole('treeitem', { name: new RegExp(sineWaveObject.name) }); - const layoutGridHolder = page.locator('.l-layout__grid-holder'); - await sineWaveGeneratorTreeItem.dragTo(layoutGridHolder); + await sineWaveGeneratorTreeItem.dragTo(page.getByLabel('Layout Grid')); await page.locator('button[title="Save"]').click(); await page.getByRole('listitem', { name: 'Save and Finish Editing' }).click(); @@ -428,8 +402,7 @@ test.describe('Display Layout', () => { const exampleImageryTreeItem = treePane.getByRole('treeitem', { name: new RegExp(exampleImageryObject.name) }); - let layoutGridHolder = page.locator('.l-layout__grid-holder'); - await exampleImageryTreeItem.dragTo(layoutGridHolder); + await exampleImageryTreeItem.dragTo(page.getByLabel('Layout Grid')); //adjust so that we can see the independent time conductor toggle // Adjust object height @@ -445,7 +418,7 @@ test.describe('Display Layout', () => { const startDate = '2021-12-30 01:01:00.000Z'; const endDate = '2021-12-30 01:11:00.000Z'; - await setIndependentTimeConductorBounds(page, { start: startDate, end: endDate }); + await setFixedIndependentTimeConductorBounds(page, { start: startDate, end: endDate }); // check image date await expect(page.getByText('2021-12-30 01:11:00.000Z').first()).toBeVisible(); @@ -485,9 +458,8 @@ test.describe('Display Layout', () => { name: new RegExp(sineWaveObject.name) }); - let layoutGridHolder = page.locator('.l-layout__grid-holder'); // eslint-disable-next-line playwright/no-force-option - await sineWaveGeneratorTreeItem.dragTo(layoutGridHolder, { force: true }); + await sineWaveGeneratorTreeItem.dragTo(page.getByLabel('Layout Grid'), { force: true }); await page.getByText('View type').click(); await page.getByText('Overlay Plot').click(); @@ -495,14 +467,13 @@ test.describe('Display Layout', () => { const anotherSineWaveGeneratorTreeItem = treePane.getByRole('treeitem', { name: new RegExp(anotherSineWaveObject.name) }); - layoutGridHolder = page.locator('.l-layout__grid-holder'); // eslint-disable-next-line playwright/no-force-option - await anotherSineWaveGeneratorTreeItem.dragTo(layoutGridHolder, { force: true }); + await anotherSineWaveGeneratorTreeItem.dragTo(page.getByLabel('Layout Grid'), { force: true }); await page.getByText('View type').click(); await page.getByText('Overlay Plot').click(); - await page.locator('button[title="Save"]').click(); + await page.getByLabel('Save').click(); await page.getByRole('listitem', { name: 'Save and Finish Editing' }).click(); // Time to inspect some network traffic @@ -519,10 +490,10 @@ test.describe('Display Layout', () => { await page.reload(); // wait for annotations requests to be batched and requested - await page.waitForLoadState('networkidle'); + await page.waitForLoadState('domcontentloaded'); // Network requests for the composite telemetry with multiple items should be: // 1. a single batched request for annotations - expect(networkRequests.length).toBe(1); + await expect.poll(() => networkRequests, { timeout: 10000 }).toHaveLength(1); await setRealTimeMode(page); @@ -531,15 +502,15 @@ test.describe('Display Layout', () => { await page.reload(); // wait for annotations to not load (if we have any, we've got a problem) - await page.waitForLoadState('networkidle'); + await page.waitForLoadState('domcontentloaded'); // In real time mode, we don't fetch annotations at all - expect(networkRequests.length).toBe(0); + await expect.poll(() => networkRequests, { timeout: 10000 }).toHaveLength(0); }); }); async function addAndRemoveDrawingObjectAndAssert(page, layoutObject, DISPLAY_LAYOUT_NAME) { - expect(await page.getByLabel(layoutObject, { exact: true }).count()).toBe(0); + await expect(page.getByLabel(layoutObject, { exact: true })).toHaveCount(0); await addLayoutObject(page, DISPLAY_LAYOUT_NAME, layoutObject); expect( await page @@ -549,7 +520,7 @@ async function addAndRemoveDrawingObjectAndAssert(page, layoutObject, DISPLAY_LA .count() ).toBe(1); await removeLayoutObject(page, layoutObject); - expect(await page.getByLabel(layoutObject, { exact: true }).count()).toBe(0); + await expect(page.getByLabel(layoutObject, { exact: true })).toHaveCount(0); } /** @@ -565,7 +536,7 @@ async function removeLayoutObject(page, layoutObject) { // eslint-disable-next-line playwright/no-force-option .click({ force: true }); await page.getByTitle('Delete the selected object').click(); - await page.getByRole('button', { name: 'OK', exact: true }).click(); + await page.getByRole('button', { name: 'Ok', exact: true }).click(); } /** @@ -584,10 +555,10 @@ async function addLayoutObject(page, layoutName, layoutObject) { .click(); if (layoutObject === 'Text') { await page.getByRole('textbox', { name: 'Text' }).fill('Hello, Universe!'); - await page.getByText('OK').click(); + await page.getByText('Ok').click(); } else if (layoutObject === 'Image') { await page.getByLabel('Image URL').fill(TINY_IMAGE_BASE64); - await page.getByText('OK').click(); + await page.getByText('Ok').click(); } } diff --git a/e2e/tests/functional/plugins/faultManagement/faultManagement.e2e.spec.js b/e2e/tests/functional/plugins/faultManagement/faultManagement.e2e.spec.js index 7b3898a34c..792c2abacc 100644 --- a/e2e/tests/functional/plugins/faultManagement/faultManagement.e2e.spec.js +++ b/e2e/tests/functional/plugins/faultManagement/faultManagement.e2e.spec.js @@ -62,17 +62,15 @@ test.describe('The Fault Management Plugin using example faults', () => { await selectFaultItem(page, 1); await page.getByRole('tab', { name: 'Config' }).click(); - const selectedFaultName = await page - .locator('.c-fault-mgmt__list.is-selected .c-fault-mgmt__list-faultname') - .textContent(); - const inspectorFaultNameCount = await page - .locator(`.c-inspector__properties >> :text("${selectedFaultName}")`) - .count(); + + const inspectorFaultName = page + .getByLabel('Source inspector properties') + .getByLabel('inspector property value'); await expect( page.locator('.c-faults-list-view-item-body > .c-fault-mgmt__list').first() ).toHaveClass(/is-selected/); - expect(inspectorFaultNameCount).toEqual(1); + await expect(inspectorFaultName).toHaveCount(1); }); test('When selecting multiple faults, no specific fault information is shown in the inspector', async ({ @@ -110,13 +108,13 @@ test.describe('The Fault Management Plugin using example faults', () => { // check it is removed from standard view const afterShelvedFault = getFaultByName(page, shelvedFaultName); - expect(await afterShelvedFault.count()).toBe(0); + await expect(afterShelvedFault).toHaveCount(0); await changeViewTo(page, 'shelved'); const shelvedViewFault = getFaultByName(page, shelvedFaultName); - expect(await shelvedViewFault.count()).toBe(1); + await expect(shelvedViewFault).toHaveCount(1); }); test('Allows you to acknowledge a fault', async ({ page }) => { diff --git a/e2e/tests/functional/plugins/flexibleLayout/flexibleLayout.e2e.spec.js b/e2e/tests/functional/plugins/flexibleLayout/flexibleLayout.e2e.spec.js index d82e4a87ee..41540834ae 100644 --- a/e2e/tests/functional/plugins/flexibleLayout/flexibleLayout.e2e.spec.js +++ b/e2e/tests/functional/plugins/flexibleLayout/flexibleLayout.e2e.spec.js @@ -24,7 +24,7 @@ import { fileURLToPath } from 'url'; import { createDomainObjectWithDefaults, - setIndependentTimeConductorBounds + setFixedIndependentTimeConductorBounds } from '../../../../appActions.js'; import { expect, test } from '../../../../pluginFixtures.js'; @@ -248,7 +248,7 @@ test.describe('Flexible Layout', () => { await page.getByRole('listitem', { name: 'Save and Finish Editing' }).click(); // flip on independent time conductor - await setIndependentTimeConductorBounds(page, { + await setFixedIndependentTimeConductorBounds(page, { start: '2021-12-30 01:01:00.000Z', end: '2021-12-30 01:11:00.000Z' }); @@ -292,7 +292,7 @@ test.describe('Flexible Layout Toolbar Actions @localStorage', () => { await expect(page.getByRole('dialog', { name: 'Overlay' })).toContainText( 'This action will permanently delete this container from this Flexible Layout. Do you want to continue?' ); - await page.getByRole('button', { name: 'OK', exact: true }).click(); + await page.getByRole('button', { name: 'Ok', exact: true }).click(); expect(await containerHandles.count()).toEqual(2); }); test('Remove Frame', async ({ page }) => { @@ -302,7 +302,7 @@ test.describe('Flexible Layout Toolbar Actions @localStorage', () => { await expect(page.getByRole('dialog', { name: 'Overlay' })).toContainText( 'This action will remove this frame from this Flexible Layout. Do you want to continue?' ); - await page.getByRole('button', { name: 'OK', exact: true }).click(); + await page.getByRole('button', { name: 'Ok', exact: true }).click(); expect(await page.getByRole('group', { name: 'Frame' }).count()).toEqual(1); }); test('Columns/Rows Layout Toggle', async ({ page }) => { diff --git a/e2e/tests/functional/plugins/gauge/gauge.e2e.spec.js b/e2e/tests/functional/plugins/gauge/gauge.e2e.spec.js index d2dd067f30..d85c89883a 100644 --- a/e2e/tests/functional/plugins/gauge/gauge.e2e.spec.js +++ b/e2e/tests/functional/plugins/gauge/gauge.e2e.spec.js @@ -38,7 +38,7 @@ test.describe('Gauge', () => { await page.goto('./', { waitUntil: 'domcontentloaded' }); }); - test('Can add and remove telemetry sources @unstable', async ({ page }) => { + test('Can add and remove telemetry sources', async ({ page }) => { // Create the gauge with defaults const gauge = await createDomainObjectWithDefaults(page, { type: 'Gauge' }); @@ -53,6 +53,7 @@ test.describe('Gauge', () => { // the SWG appears in the elements pool await page.goto(gauge.url); await page.getByLabel('Edit Object').click(); + await page.getByRole('tab', { name: 'Elements' }).click(); await expect.soft(page.locator(`#inspector-elements-tree >> text=${swg1.name}`)).toBeVisible(); await page.getByRole('button', { name: 'Save' }).click(); await page.getByRole('listitem', { name: 'Save and Finish Editing' }).click(); @@ -65,38 +66,35 @@ test.describe('Gauge', () => { }); // Verify that the 'Replace telemetry source' modal appears and accept it - await expect - .soft( - page.locator( - 'text=This action will replace the current telemetry source. Do you want to continue?' - ) + await expect( + page.getByText( + 'This action will replace the current telemetry source. Do you want to continue?' ) - .toBeVisible(); - await page.click('text=Ok'); + ).toBeVisible(); + await page.getByRole('button', { name: 'Ok', exact: true }).click(); // Navigate to the gauge and verify that the new SWG // appears in the elements pool and the old one is gone await page.goto(gauge.url); await page.getByLabel('Edit Object').click(); - await expect.soft(page.locator(`#inspector-elements-tree >> text=${swg1.name}`)).toBeHidden(); - await expect.soft(page.locator(`#inspector-elements-tree >> text=${swg2.name}`)).toBeVisible(); + await page.getByRole('tab', { name: 'Elements' }).click(); + await expect(page.getByLabel(`Preview ${swg1.name}`)).toBeHidden(); + await expect(page.getByLabel(`Preview ${swg2.name}`)).toBeVisible(); await page.getByRole('button', { name: 'Save' }).click(); // Right click on the new SWG in the elements pool and delete it - await page.locator(`#inspector-elements-tree >> text=${swg2.name}`).click({ + await page.getByLabel(`Preview ${swg2.name}`).click({ button: 'right' }); - await page.locator('li[title="Remove this object from its containing object."]').click(); + await page.getByLabel('Remove').click(); // Verify that the 'Remove object' confirmation modal appears and accept it - await expect - .soft( - page.locator( - 'text=Warning! This action will remove this object. Are you sure you want to continue?' - ) + await expect( + page.getByText( + 'Warning! This action will remove this object. Are you sure you want to continue?' ) - .toBeVisible(); - await page.click('text=Ok'); + ).toBeVisible(); + await page.getByRole('button', { name: 'Ok', exact: true }).click(); // Verify that the elements pool shows no elements await expect(page.locator('text="No contained elements"')).toBeVisible(); @@ -110,11 +108,11 @@ test.describe('Gauge', () => { await page.getByRole('button', { name: 'Create' }).click(); // Click the object specified by 'type' - await page.click(`li[role='menuitem']:text("Gauge")`); + await page.getByRole('menuitem', { name: 'Gauge' }).click(); // FIXME: We need better selectors for these custom form controls const displayCurrentValueSwitch = page.locator('.c-toggle-switch__slider >> nth=0'); - await displayCurrentValueSwitch.setChecked(false); - await page.click('button[aria-label="Save"]'); + await displayCurrentValueSwitch.uncheck(); + await page.getByLabel('Save').click(); // TODO: Verify changes in the UI }); @@ -126,24 +124,21 @@ test.describe('Gauge', () => { // Create the gauge with defaults await createDomainObjectWithDefaults(page, { type: 'Gauge' }); - await page.click('button[title="More actions"]'); - await page.click('li[role="menuitem"]:has-text("Edit Properties")'); + await page.getByLabel('More actions').click(); + await page.getByRole('menuitem', { name: 'Edit Properties...' }).click(); // FIXME: We need better selectors for these custom form controls const displayCurrentValueSwitch = page.locator('.c-toggle-switch__slider >> nth=0'); - await displayCurrentValueSwitch.setChecked(false); - await page.click('button[aria-label="Save"]'); + await displayCurrentValueSwitch.uncheck(); + await page.getByLabel('Save').click(); // TODO: Verify changes in the UI }); - test.fixme('Gauge does not display NaN when data not available', async ({ page }) => { - test.info().annotations.push({ - type: 'issue', - description: 'https://github.com/nasa/openmct/issues/7421' - }); + test('Gauge does not display NaN when data not available', async ({ page }) => { // Create a Gauge const gauge = await createDomainObjectWithDefaults(page, { - type: 'Gauge' + type: 'Gauge', + name: 'Gauge with no data' }); // Create a Sine Wave Generator in the Gauge with a loading delay @@ -154,7 +149,7 @@ test.describe('Gauge', () => { await page.getByRole('menuitem', { name: /Edit Properties.../ }).click(); //Edit Example Telemetry Object to include 5s loading Delay - await page.locator('[aria-label="Loading Delay \\(ms\\)"]').fill('5000'); + await page.getByLabel('Loading Delay (ms)', { exact: true }).fill('5000'); await page.getByRole('button', { name: 'Save' }).click(); @@ -162,9 +157,13 @@ test.describe('Gauge', () => { await page.waitForURL(`**/${gauge.uuid}/*`); // Nav to the Gauge - await page.goto(gauge.url); - const gaugeNoDataText = await page.locator('.js-dial-current-value tspan').textContent(); - expect(gaugeNoDataText).toBe('--'); + await page.goto(gauge.url, { waitUntil: 'domcontentloaded' }); + // Check that the value is not displayed + //TODO https://github.com/nasa/openmct/issues/7790 update this locator + await expect(page.getByTitle('Value is currently out of')).toHaveAttribute( + 'aria-valuenow', + '--' + ); }); test('Gauge enforces composition policy', async ({ page }) => { diff --git a/e2e/tests/functional/plugins/imagery/exampleImagery.e2e.spec.js b/e2e/tests/functional/plugins/imagery/exampleImagery.e2e.spec.js index 224bc519ba..d75e104e0d 100644 --- a/e2e/tests/functional/plugins/imagery/exampleImagery.e2e.spec.js +++ b/e2e/tests/functional/plugins/imagery/exampleImagery.e2e.spec.js @@ -75,6 +75,7 @@ test.describe('Example Imagery Object', () => { const backgroundImage = page.getByLabel('Focused Image Element'); await backgroundImage.click({ button: 'right', + // Need force option here due to annotation overlay which blocks playwright's click // eslint-disable-next-line playwright/no-force-option force: true }); @@ -351,8 +352,8 @@ test.describe('Example Imagery Object', () => { }); test('Uses low fetch priority', async ({ page }) => { - const priority = await page.locator('.js-imageryView-image').getAttribute('fetchpriority'); - expect(priority).toBe('low'); + const priority = page.locator('.js-imageryView-image'); + await expect(priority).toHaveAttribute('fetchpriority', 'low'); }); }); @@ -362,7 +363,7 @@ test.describe('Example Imagery in Display Layout @clock', () => { test.beforeEach(async ({ page }) => { // We mock the clock so that we don't need to wait for time driven events // to verify functionality. - await page.clock.setSystemTime(MISSION_TIME); + await page.clock.install({ time: MISSION_TIME }); await page.clock.resume(); // Go to baseURL @@ -447,6 +448,8 @@ test.describe('Example Imagery in Display Layout @clock', () => { await page.locator('div[title="Resize object width"] > input').click(); await page.locator('div[title="Resize object width"] > input').fill('50'); + await expect(page.getByLabel('Image Thumbnail from').last()).toBeInViewport(); + await performImageryViewOperationsAndAssert(page, displayLayout); }); @@ -528,7 +531,7 @@ test.describe('Example Imagery in Flexible layout @clock', () => { test.beforeEach(async ({ page }) => { // We mock the clock so that we don't need to wait for time driven events // to verify functionality. - await page.clock.setSystemTime(MISSION_TIME); + await page.clock.install({ time: MISSION_TIME }); await page.clock.resume(); await page.goto('./', { waitUntil: 'domcontentloaded' }); @@ -543,6 +546,8 @@ test.describe('Example Imagery in Flexible layout @clock', () => { // Navigate back to Flexible Layout await page.goto(flexibleLayout.url); + // Wait for image thumbnail auto-scroll to complete + await expect(page.getByLabel('Image Thumbnail from').last()).toBeInViewport(); }); test('Can double-click on the image to view large image', async ({ page }) => { @@ -573,7 +578,7 @@ test.describe('Example Imagery in Tabs View @clock', () => { test.beforeEach(async ({ page }) => { // We mock the clock so that we don't need to wait for time driven events // to verify functionality. - await page.clock.setSystemTime(MISSION_TIME); + await page.clock.install({ time: MISSION_TIME }); await page.clock.resume(); await page.goto('./', { waitUntil: 'domcontentloaded' }); @@ -586,25 +591,21 @@ test.describe('Example Imagery in Tabs View @clock', () => { await page.getByRole('button', { name: 'Create' }).click(); // Click text=Example Imagery - await page.click('li[role="menuitem"]:has-text("Example Imagery")'); + await page.getByRole('menuitem', { name: 'Example Imagery' }).click(); // Clear and set Image load delay to minimum value await page.locator('input[type="number"]').clear(); await page.locator('input[type="number"]').fill(`${IMAGE_LOAD_DELAY}`); - // Click text=OK - await Promise.all([ - page.waitForNavigation({ waitUntil: 'networkidle' }), - page.click('button:has-text("OK")'), - //Wait for Save Banner to appear - page.waitForSelector('.c-message-banner__message') - ]); + await page.getByLabel('Save').click(); await expect(page.locator('.l-browse-bar__object-name')).toContainText( 'Unnamed Example Imagery' ); await page.goto(tabsView.url); + // Wait for image thumbnail auto-scroll to complete + await expect(page.getByLabel('Image Thumbnail from').last()).toBeInViewport(); }); test('Imagery View operations @clock', async ({ page }) => { await performImageryViewOperationsAndAssert(page, tabsView); @@ -836,8 +837,7 @@ async function assertBackgroundImageUrlFromBackgroundCss(page) { * @param {import('@playwright/test').Page} page */ async function panZoomAndAssertImageProperties(page) { - const imageryHintsText = await page.locator('.c-imagery__hints').innerText(); - expect(expectedAltText).toEqual(imageryHintsText); + await expect(page.locator('.c-imagery__hints')).toContainText(expectedAltText); const zoomedBoundingBox = await page.getByLabel('Focused Image Element').boundingBox(); const imageCenterX = zoomedBoundingBox.x + zoomedBoundingBox.width / 2; const imageCenterY = zoomedBoundingBox.y + zoomedBoundingBox.height / 2; @@ -1056,7 +1056,6 @@ async function createImageryViewWithShortDelay(page, { name, parent }) { /** * @param {import('@playwright/test').Page} page */ -// eslint-disable-next-line require-await async function waitForZoomAndPanTransitions(page) { // Wait for image to stabilize await page.getByLabel('Focused Image Element').hover({ trial: true }); diff --git a/e2e/tests/functional/plugins/importAndExportAsJSON/exportAsJson.e2e.spec.js b/e2e/tests/functional/plugins/importAndExportAsJSON/exportAsJson.e2e.spec.js index e8cc3c6344..753ae18d9d 100644 --- a/e2e/tests/functional/plugins/importAndExportAsJSON/exportAsJson.e2e.spec.js +++ b/e2e/tests/functional/plugins/importAndExportAsJSON/exportAsJson.e2e.spec.js @@ -26,10 +26,7 @@ This test suite is dedicated to tests which verify the basic operations surround import fs from 'fs/promises'; -import { - createDomainObjectWithDefaults, - openObjectTreeContextMenu -} from '../../../../appActions.js'; +import { createDomainObjectWithDefaults } from '../../../../appActions.js'; import { expect, test } from '../../../../baseFixtures.js'; import { navigateToFaultManagementWithExample } from '../../../../helper/faultUtils.js'; @@ -51,7 +48,10 @@ test.describe('ExportAsJSON', () => { await page.goto(folder.url); // Open context menu and initiate download - await openObjectTreeContextMenu(page, folder.url); + await page.getByLabel('Show selected item in tree').click(); + await page.getByRole('treeitem', { name: 'Expand e2e folder folder' }).click({ + button: 'right' + }); const [download] = await Promise.all([ page.waitForEvent('download'), // Waits for the download event page.getByLabel('Export as JSON').click() // Triggers the download @@ -105,7 +105,12 @@ test.describe('ExportAsJSON', () => { await page.goto(timer.url); //do this against parent folder.url, NOT timer.url child - await openObjectTreeContextMenu(page, folder.url); + // Open context menu and initiate download + await page.getByLabel('Show selected item in tree').click(); + await page.getByRole('treeitem', { name: 'Collapse e2e folder folder' }).click({ + button: 'right' + }); + // Open context menu and initiate download const [download] = await Promise.all([ page.waitForEvent('download'), // Waits for the download event @@ -141,18 +146,18 @@ test.describe('ExportAsJSON Disabled Actions', () => { }); test('Verify that the ExportAsJSON dropdown does not appear for the item X', async ({ page }) => { await page.getByLabel('More actions').click(); - await expect(await page.getByLabel('Export as JSON')).toHaveCount(0); + await expect(page.getByLabel('Export as JSON')).toHaveCount(0); await page.getByRole('treeitem', { name: 'Fault Management' }).click({ button: 'right' }); - await expect(await page.getByLabel('Export as JSON')).toHaveCount(0); + await expect(page.getByLabel('Export as JSON')).toHaveCount(0); }); }); test.describe('ExportAsJSON ProgressBar @couchdb', () => { let folder; test.beforeEach(async ({ page }) => { - await page.goto('./', { waitUntil: 'networkidle' }); + await page.goto('./', { waitUntil: 'domcontentloaded' }); // Perform actions to create the domain object folder = await createDomainObjectWithDefaults(page, { type: 'Folder' diff --git a/e2e/tests/functional/plugins/inspectorDataVisualization/numericData.e2e.spec.js b/e2e/tests/functional/plugins/inspectorDataVisualization/numericData.e2e.spec.js index d8ff8df76e..80e28bb5cd 100644 --- a/e2e/tests/functional/plugins/inspectorDataVisualization/numericData.e2e.spec.js +++ b/e2e/tests/functional/plugins/inspectorDataVisualization/numericData.e2e.spec.js @@ -36,7 +36,7 @@ test.describe('Testing numeric data with inspector data visualization (i.e., dat }); test('Can click on telemetry and see data in inspector @2p', async ({ page, context }) => { - const initStartBounds = await page.getByLabel('Start bounds').textContent(); + const initStartBounds = page.getByLabel('Start bounds'); const initEndBounds = await page.getByLabel('End bounds').textContent(); const exampleDataVisualizationSource = await createDomainObjectWithDefaults(page, { type: 'Example Data Visualization Source' @@ -81,7 +81,9 @@ test.describe('Testing numeric data with inspector data visualization (i.e., dat await expect(newPage).toHaveTitle('Second Sine Wave Generator'); // Verify that "Open in New Tab" preserves the time bounds - expect(initStartBounds).toEqual(await newPage.getByLabel('Start bounds').textContent()); + await expect(initStartBounds).toHaveText( + await newPage.getByLabel('Start bounds').textContent() + ); expect(initEndBounds).toEqual(await newPage.getByLabel('End bounds').textContent()); }); }); diff --git a/e2e/tests/functional/plugins/lad/lad.e2e.spec.js b/e2e/tests/functional/plugins/lad/lad.e2e.spec.js index 289b1690e8..fd2f385bd7 100644 --- a/e2e/tests/functional/plugins/lad/lad.e2e.spec.js +++ b/e2e/tests/functional/plugins/lad/lad.e2e.spec.js @@ -22,7 +22,7 @@ import { createDomainObjectWithDefaults, - openObjectTreeContextMenu, + navigateToObjectWithRealTime, setFixedTimeMode, setRealTimeMode, setStartOffset @@ -56,61 +56,61 @@ test.describe('Testing LAD table configuration', () => { await page.getByRole('tab', { name: 'LAD Table Configuration' }).click(); // make sure headers are visible initially - await expect(page.getByRole('cell', { name: 'Timestamp', exact: true })).toBeVisible(); - await expect(page.getByRole('cell', { name: 'Units' })).toBeVisible(); - await expect(page.getByRole('cell', { name: 'Type', exact: true })).toBeVisible(); - await expect(page.getByRole('cell', { name: 'WATCH' })).toBeVisible(); - await expect(page.getByRole('cell', { name: 'WARNING' })).toBeVisible(); - await expect(page.getByRole('cell', { name: 'DISTRESS' })).toBeVisible(); - await expect(page.getByRole('cell', { name: 'CRITICAL' })).toBeVisible(); - await expect(page.getByRole('cell', { name: 'SEVERE' })).toBeVisible(); + await expect(page.getByRole('columnheader', { name: 'Timestamp', exact: true })).toBeVisible(); + await expect(page.getByRole('columnheader', { name: 'Units' })).toBeVisible(); + await expect(page.getByRole('columnheader', { name: 'Type', exact: true })).toBeVisible(); + await expect(page.getByRole('columnheader', { name: 'Limit WATCH' })).toBeVisible(); + await expect(page.getByRole('columnheader', { name: 'Limit WARNING' })).toBeVisible(); + await expect(page.getByRole('columnheader', { name: 'Limit DISTRESS' })).toBeVisible(); + await expect(page.getByRole('columnheader', { name: 'Limit CRITICAL' })).toBeVisible(); + await expect(page.getByRole('columnheader', { name: 'Limit SEVERE' })).toBeVisible(); // hide timestamp column await page.getByLabel('Timestamp', { exact: true }).uncheck(); - await expect(page.getByRole('cell', { name: 'Timestamp', exact: true })).toBeHidden(); - await expect(page.getByRole('cell', { name: 'Units' })).toBeVisible(); - await expect(page.getByRole('cell', { name: 'Type', exact: true })).toBeVisible(); - await expect(page.getByRole('cell', { name: 'WATCH' })).toBeVisible(); - await expect(page.getByRole('cell', { name: 'WARNING' })).toBeVisible(); - await expect(page.getByRole('cell', { name: 'DISTRESS' })).toBeVisible(); - await expect(page.getByRole('cell', { name: 'CRITICAL' })).toBeVisible(); - await expect(page.getByRole('cell', { name: 'SEVERE' })).toBeVisible(); + await expect(page.getByRole('columnheader', { name: 'Timestamp', exact: true })).toBeHidden(); + await expect(page.getByRole('columnheader', { name: 'Units' })).toBeVisible(); + await expect(page.getByRole('columnheader', { name: 'Type', exact: true })).toBeVisible(); + await expect(page.getByRole('columnheader', { name: 'Limit WATCH' })).toBeVisible(); + await expect(page.getByRole('columnheader', { name: 'Limit WARNING' })).toBeVisible(); + await expect(page.getByRole('columnheader', { name: 'Limit DISTRESS' })).toBeVisible(); + await expect(page.getByRole('columnheader', { name: 'Limit CRITICAL' })).toBeVisible(); + await expect(page.getByRole('columnheader', { name: 'Limit SEVERE' })).toBeVisible(); // hide units & type column await page.getByLabel('Units').uncheck(); await page.getByLabel('Type', { exact: true }).uncheck(); - await expect(page.getByRole('cell', { name: 'Timestamp', exact: true })).toBeHidden(); - await expect(page.getByRole('cell', { name: 'Units' })).toBeHidden(); - await expect(page.getByRole('cell', { name: 'Type', exact: true })).toBeHidden(); - await expect(page.getByRole('cell', { name: 'WATCH' })).toBeVisible(); - await expect(page.getByRole('cell', { name: 'WARNING' })).toBeVisible(); - await expect(page.getByRole('cell', { name: 'DISTRESS' })).toBeVisible(); - await expect(page.getByRole('cell', { name: 'CRITICAL' })).toBeVisible(); - await expect(page.getByRole('cell', { name: 'SEVERE' })).toBeVisible(); + await expect(page.getByRole('columnheader', { name: 'Timestamp', exact: true })).toBeHidden(); + await expect(page.getByRole('columnheader', { name: 'Units' })).toBeHidden(); + await expect(page.getByRole('columnheader', { name: 'Type', exact: true })).toBeHidden(); + await expect(page.getByRole('columnheader', { name: 'Limit WATCH' })).toBeVisible(); + await expect(page.getByRole('columnheader', { name: 'Limit WARNING' })).toBeVisible(); + await expect(page.getByRole('columnheader', { name: 'Limit DISTRESS' })).toBeVisible(); + await expect(page.getByRole('columnheader', { name: 'Limit CRITICAL' })).toBeVisible(); + await expect(page.getByRole('columnheader', { name: 'Limit SEVERE' })).toBeVisible(); // hide WATCH column await page.getByLabel('WATCH').uncheck(); - await expect(page.getByRole('cell', { name: 'Timestamp', exact: true })).toBeHidden(); - await expect(page.getByRole('cell', { name: 'Units' })).toBeHidden(); - await expect(page.getByRole('cell', { name: 'Type', exact: true })).toBeHidden(); - await expect(page.getByRole('cell', { name: 'WATCH' })).toBeHidden(); - await expect(page.getByRole('cell', { name: 'WARNING' })).toBeVisible(); - await expect(page.getByRole('cell', { name: 'DISTRESS' })).toBeVisible(); - await expect(page.getByRole('cell', { name: 'CRITICAL' })).toBeVisible(); - await expect(page.getByRole('cell', { name: 'SEVERE' })).toBeVisible(); + await expect(page.getByRole('columnheader', { name: 'Timestamp', exact: true })).toBeHidden(); + await expect(page.getByRole('columnheader', { name: 'Units' })).toBeHidden(); + await expect(page.getByRole('columnheader', { name: 'Type', exact: true })).toBeHidden(); + await expect(page.getByRole('columnheader', { name: 'Limit WATCH' })).toBeHidden(); + await expect(page.getByRole('columnheader', { name: 'Limit WARNING' })).toBeVisible(); + await expect(page.getByRole('columnheader', { name: 'Limit DISTRESS' })).toBeVisible(); + await expect(page.getByRole('columnheader', { name: 'Limit CRITICAL' })).toBeVisible(); + await expect(page.getByRole('columnheader', { name: 'Limit SEVERE' })).toBeVisible(); // save and reload and verify they columns are still hidden - await page.locator('button[title="Save"]').click(); + await page.getByRole('button', { name: 'Save' }).click(); await page.getByRole('listitem', { name: 'Save and Finish Editing' }).click(); await page.reload(); - await expect(page.getByRole('cell', { name: 'Timestamp', exact: true })).toBeHidden(); - await expect(page.getByRole('cell', { name: 'Units' })).toBeHidden(); - await expect(page.getByRole('cell', { name: 'Type', exact: true })).toBeHidden(); - await expect(page.getByRole('cell', { name: 'WATCH' })).toBeHidden(); - await expect(page.getByRole('cell', { name: 'WARNING' })).toBeVisible(); - await expect(page.getByRole('cell', { name: 'DISTRESS' })).toBeVisible(); - await expect(page.getByRole('cell', { name: 'CRITICAL' })).toBeVisible(); - await expect(page.getByRole('cell', { name: 'SEVERE' })).toBeVisible(); + await expect(page.getByRole('columnheader', { name: 'Timestamp', exact: true })).toBeHidden(); + await expect(page.getByRole('columnheader', { name: 'Units' })).toBeHidden(); + await expect(page.getByRole('columnheader', { name: 'Type', exact: true })).toBeHidden(); + await expect(page.getByRole('columnheader', { name: 'Limit WATCH' })).toBeHidden(); + await expect(page.getByRole('columnheader', { name: 'Limit WARNING' })).toBeVisible(); + await expect(page.getByRole('columnheader', { name: 'Limit DISTRESS' })).toBeVisible(); + await expect(page.getByRole('columnheader', { name: 'Limit CRITICAL' })).toBeVisible(); + await expect(page.getByRole('columnheader', { name: 'Limit SEVERE' })).toBeVisible(); // Edit LAD table await page.getByLabel('Edit Object').click(); @@ -118,27 +118,27 @@ test.describe('Testing LAD table configuration', () => { // show timestamp column await page.getByLabel('Timestamp', { exact: true }).check(); - await expect(page.getByRole('cell', { name: 'Units' })).toBeHidden(); - await expect(page.getByRole('cell', { name: 'Type', exact: true })).toBeHidden(); - await expect(page.getByRole('cell', { name: 'Timestamp', exact: true })).toBeVisible(); - await expect(page.getByRole('cell', { name: 'WATCH' })).toBeHidden(); - await expect(page.getByRole('cell', { name: 'WARNING' })).toBeVisible(); - await expect(page.getByRole('cell', { name: 'DISTRESS' })).toBeVisible(); - await expect(page.getByRole('cell', { name: 'CRITICAL' })).toBeVisible(); - await expect(page.getByRole('cell', { name: 'SEVERE' })).toBeVisible(); + await expect(page.getByRole('columnheader', { name: 'Units' })).toBeHidden(); + await expect(page.getByRole('columnheader', { name: 'Type', exact: true })).toBeHidden(); + await expect(page.getByRole('columnheader', { name: 'Timestamp', exact: true })).toBeVisible(); + await expect(page.getByRole('columnheader', { name: 'Limit WATCH' })).toBeHidden(); + await expect(page.getByRole('columnheader', { name: 'Limit WARNING' })).toBeVisible(); + await expect(page.getByRole('columnheader', { name: 'Limit DISTRESS' })).toBeVisible(); + await expect(page.getByRole('columnheader', { name: 'Limit CRITICAL' })).toBeVisible(); + await expect(page.getByRole('columnheader', { name: 'Limit SEVERE' })).toBeVisible(); // save and reload and make sure timestamp is still visible - await page.locator('button[title="Save"]').click(); + await page.getByRole('button', { name: 'Save' }).click(); await page.getByRole('listitem', { name: 'Save and Finish Editing' }).click(); await page.reload(); - await expect(page.getByRole('cell', { name: 'Units' })).toBeHidden(); - await expect(page.getByRole('cell', { name: 'Type', exact: true })).toBeHidden(); - await expect(page.getByRole('cell', { name: 'Timestamp', exact: true })).toBeVisible(); - await expect(page.getByRole('cell', { name: 'WATCH' })).toBeHidden(); - await expect(page.getByRole('cell', { name: 'WARNING' })).toBeVisible(); - await expect(page.getByRole('cell', { name: 'DISTRESS' })).toBeVisible(); - await expect(page.getByRole('cell', { name: 'CRITICAL' })).toBeVisible(); - await expect(page.getByRole('cell', { name: 'SEVERE' })).toBeVisible(); + await expect(page.getByRole('columnheader', { name: 'Units' })).toBeHidden(); + await expect(page.getByRole('columnheader', { name: 'Type', exact: true })).toBeHidden(); + await expect(page.getByRole('columnheader', { name: 'Timestamp', exact: true })).toBeVisible(); + await expect(page.getByRole('columnheader', { name: 'Limit WATCH' })).toBeHidden(); + await expect(page.getByRole('columnheader', { name: 'Limit WARNING' })).toBeVisible(); + await expect(page.getByRole('columnheader', { name: 'Limit DISTRESS' })).toBeVisible(); + await expect(page.getByRole('columnheader', { name: 'Limit CRITICAL' })).toBeVisible(); + await expect(page.getByRole('columnheader', { name: 'Limit SEVERE' })).toBeVisible(); // Edit LAD table await page.getByLabel('Edit Object').click(); @@ -148,27 +148,27 @@ test.describe('Testing LAD table configuration', () => { await page.getByLabel('Units').check(); await page.getByLabel('Type', { exact: true }).check(); await page.getByLabel('WATCH').check(); - await expect(page.getByRole('cell', { name: 'Timestamp', exact: true })).toBeVisible(); - await expect(page.getByRole('cell', { name: 'Units' })).toBeVisible(); - await expect(page.getByRole('cell', { name: 'Type', exact: true })).toBeVisible(); - await expect(page.getByRole('cell', { name: 'WATCH' })).toBeVisible(); - await expect(page.getByRole('cell', { name: 'WARNING' })).toBeVisible(); - await expect(page.getByRole('cell', { name: 'DISTRESS' })).toBeVisible(); - await expect(page.getByRole('cell', { name: 'CRITICAL' })).toBeVisible(); - await expect(page.getByRole('cell', { name: 'SEVERE' })).toBeVisible(); + await expect(page.getByRole('columnheader', { name: 'Timestamp', exact: true })).toBeVisible(); + await expect(page.getByRole('columnheader', { name: 'Units' })).toBeVisible(); + await expect(page.getByRole('columnheader', { name: 'Type', exact: true })).toBeVisible(); + await expect(page.getByRole('columnheader', { name: 'Limit WATCH' })).toBeVisible(); + await expect(page.getByRole('columnheader', { name: 'Limit WARNING' })).toBeVisible(); + await expect(page.getByRole('columnheader', { name: 'Limit DISTRESS' })).toBeVisible(); + await expect(page.getByRole('columnheader', { name: 'Limit CRITICAL' })).toBeVisible(); + await expect(page.getByRole('columnheader', { name: 'Limit SEVERE' })).toBeVisible(); // save and reload and make sure all columns are still visible await page.locator('button[title="Save"]').click(); await page.getByRole('listitem', { name: 'Save and Finish Editing' }).click(); await page.reload(); - await expect(page.getByRole('cell', { name: 'Timestamp', exact: true })).toBeVisible(); - await expect(page.getByRole('cell', { name: 'Units' })).toBeVisible(); - await expect(page.getByRole('cell', { name: 'Type', exact: true })).toBeVisible(); - await expect(page.getByRole('cell', { name: 'WATCH' })).toBeVisible(); - await expect(page.getByRole('cell', { name: 'WARNING' })).toBeVisible(); - await expect(page.getByRole('cell', { name: 'DISTRESS' })).toBeVisible(); - await expect(page.getByRole('cell', { name: 'CRITICAL' })).toBeVisible(); - await expect(page.getByRole('cell', { name: 'SEVERE' })).toBeVisible(); + await expect(page.getByRole('columnheader', { name: 'Timestamp', exact: true })).toBeVisible(); + await expect(page.getByRole('columnheader', { name: 'Units' })).toBeVisible(); + await expect(page.getByRole('columnheader', { name: 'Type', exact: true })).toBeVisible(); + await expect(page.getByRole('columnheader', { name: 'Limit WATCH' })).toBeVisible(); + await expect(page.getByRole('columnheader', { name: 'Limit WARNING' })).toBeVisible(); + await expect(page.getByRole('columnheader', { name: 'Limit DISTRESS' })).toBeVisible(); + await expect(page.getByRole('columnheader', { name: 'Limit CRITICAL' })).toBeVisible(); + await expect(page.getByRole('columnheader', { name: 'Limit SEVERE' })).toBeVisible(); }); test('When adding something without Units, do not show Units column', async ({ page }) => { @@ -185,14 +185,14 @@ test.describe('Testing LAD table configuration', () => { await page.getByRole('tab', { name: 'LAD Table Configuration' }).click(); // make sure Sine Wave headers are visible initially too - await expect(page.getByRole('cell', { name: 'Timestamp', exact: true })).toBeVisible(); - await expect(page.getByRole('cell', { name: 'Units' })).toBeVisible(); - await expect(page.getByRole('cell', { name: 'Type', exact: true })).toBeVisible(); - await expect(page.getByRole('cell', { name: 'WATCH' })).toBeVisible(); - await expect(page.getByRole('cell', { name: 'WARNING' })).toBeVisible(); - await expect(page.getByRole('cell', { name: 'DISTRESS' })).toBeVisible(); - await expect(page.getByRole('cell', { name: 'CRITICAL' })).toBeVisible(); - await expect(page.getByRole('cell', { name: 'SEVERE' })).toBeVisible(); + await expect(page.getByRole('columnheader', { name: 'Timestamp', exact: true })).toBeVisible(); + await expect(page.getByRole('columnheader', { name: 'Units' })).toBeVisible(); + await expect(page.getByRole('columnheader', { name: 'Type', exact: true })).toBeVisible(); + await expect(page.getByRole('columnheader', { name: 'Limit WATCH' })).toBeVisible(); + await expect(page.getByRole('columnheader', { name: 'Limit WARNING' })).toBeVisible(); + await expect(page.getByRole('columnheader', { name: 'Limit DISTRESS' })).toBeVisible(); + await expect(page.getByRole('columnheader', { name: 'Limit CRITICAL' })).toBeVisible(); + await expect(page.getByRole('columnheader', { name: 'Limit SEVERE' })).toBeVisible(); // save and reload and verify they columns are still hidden await page.getByLabel('Save').click(); @@ -201,25 +201,25 @@ test.describe('Testing LAD table configuration', () => { // Remove Sine Wave Generator openObjectTreeContextMenu(page, sineWaveObject.url); await page.getByRole('menuitem', { name: /Remove/ }).click(); - await page.getByRole('button', { name: 'OK', exact: true }).click(); + await page.getByRole('button', { name: 'Ok', exact: true }).click(); // Ensure Units & Limit columns are gone // as Event Generator don't have them await page.goto(ladTable.url); - await expect(page.getByRole('cell', { name: 'Timestamp', exact: true })).toBeVisible(); - await expect(page.getByRole('cell', { name: 'Type', exact: true })).toBeVisible(); - await expect(page.getByRole('cell', { name: 'Units' })).toBeHidden(); - await expect(page.getByRole('cell', { name: 'WATCH' })).toBeHidden(); - await expect(page.getByRole('cell', { name: 'WARNING' })).toBeHidden(); - await expect(page.getByRole('cell', { name: 'DISTRESS' })).toBeHidden(); - await expect(page.getByRole('cell', { name: 'CRITICAL' })).toBeHidden(); - await expect(page.getByRole('cell', { name: 'SEVERE' })).toBeHidden(); + await expect(page.getByRole('columnheader', { name: 'Timestamp', exact: true })).toBeVisible(); + await expect(page.getByRole('columnheader', { name: 'Type', exact: true })).toBeVisible(); + await expect(page.getByRole('columnheader', { name: 'Units' })).toBeHidden(); + await expect(page.getByRole('columnheader', { name: 'Limit WATCH' })).toBeHidden(); + await expect(page.getByRole('columnheader', { name: 'Limit WARNING' })).toBeHidden(); + await expect(page.getByRole('columnheader', { name: 'Limit DISTRESS' })).toBeHidden(); + await expect(page.getByRole('columnheader', { name: 'Limit CRITICAL' })).toBeHidden(); + await expect(page.getByRole('columnheader', { name: 'Limit SEVERE' })).toBeHidden(); }); test("LAD Tables don't allow selection of rows but does show context click menus", async ({ page }) => { - const cell = await page.locator('.js-first-data'); + const cell = page.locator('.js-first-data'); const userSelectable = await cell.evaluate((el) => { return window.getComputedStyle(el).getPropertyValue('user-select'); }); @@ -237,19 +237,21 @@ test.describe('Testing LAD table configuration', () => { }); }); -test.describe('Testing LAD table @unstable', () => { +test.describe('Testing LAD table', () => { let sineWaveObject; test.beforeEach(async ({ page }) => { await page.goto('./', { waitUntil: 'domcontentloaded' }); - await setRealTimeMode(page); // Create Sine Wave Generator sineWaveObject = await createDomainObjectWithDefaults(page, { type: 'Sine Wave Generator', name: 'Test Sine Wave Generator' }); + + // Switch to real time mode by navigating directly to the URL + await navigateToObjectWithRealTime(page, sineWaveObject.url); }); - test('telemetry value exactly matches latest telemetry value received in real time', async ({ + test('telemetry value exactly matches latest telemetry value received in realtime mode', async ({ page }) => { // Create LAD table @@ -261,23 +263,23 @@ test.describe('Testing LAD table @unstable', () => { await page.getByLabel('Edit Object').click(); // Expand the 'My Items' folder in the left tree - await page.locator('.c-tree__item__view-control.c-disclosure-triangle').click(); + await page.getByLabel('Expand My Items').click(); // Add the Sine Wave Generator to the LAD table and save changes - await page.dragAndDrop('text=Test Sine Wave Generator', '.c-lad-table-wrapper'); - await page.locator('button[title="Save"]').click(); + await page.getByLabel('Preview Test Sine Wave').dragTo(page.locator('#lad-table-drop-area')); + await page.getByRole('button', { name: 'Save' }).click(); await page.getByRole('listitem', { name: 'Save and Finish Editing' }).click(); // Subscribe to the Sine Wave Generator data // On getting data, check if the value found in the LAD table is the most recent value // from the Sine Wave Generator - const getTelemValuePromise = await subscribeToTelemetry(page, sineWaveObject.uuid); + const getTelemValuePromise = subscribeToTelemetry(page, sineWaveObject.uuid); const subscribeTelemValue = await getTelemValuePromise; - const ladTableValuePromise = await page.waitForSelector(`text="${subscribeTelemValue}"`); - const ladTableValue = await ladTableValuePromise.textContent(); + await expect(page.getByLabel('lad value')).toHaveText(subscribeTelemValue); + const ladTableValue = await page.getByText(subscribeTelemValue).textContent(); - expect(ladTableValue).toBe(subscribeTelemValue); + expect(ladTableValue).toEqual(subscribeTelemValue); }); - test('telemetry value exactly matches latest telemetry value received in fixed time', async ({ + test('telemetry value exactly matches latest telemetry value received in fixed time mode', async ({ page }) => { // Create LAD table @@ -289,25 +291,23 @@ test.describe('Testing LAD table @unstable', () => { await page.getByLabel('Edit Object').click(); // Expand the 'My Items' folder in the left tree - await page.locator('.c-tree__item__view-control.c-disclosure-triangle').click(); + await page.getByLabel('Expand My Items').click(); // Add the Sine Wave Generator to the LAD table and save changes - await page.dragAndDrop('text=Test Sine Wave Generator', '.c-lad-table-wrapper'); - await page.locator('button[title="Save"]').click(); + await page.getByLabel('Preview Test Sine Wave').dragTo(page.locator('#lad-table-drop-area')); + await page.getByRole('button', { name: 'Save' }).click(); await page.getByRole('listitem', { name: 'Save and Finish Editing' }).click(); // Subscribe to the Sine Wave Generator data - const getTelemValuePromise = await subscribeToTelemetry(page, sineWaveObject.uuid); + const getTelemValuePromise = subscribeToTelemetry(page, sineWaveObject.uuid); // Set an offset of 1 minute and then change the time mode to fixed to set a 1 minute historical window - await setStartOffset(page, { mins: '1' }); + await setRealTimeMode(page); + await setStartOffset(page, { startMins: '01' }); await setFixedTimeMode(page); // On getting data, check if the value found in the LAD table is the most recent value // from the Sine Wave Generator const subscribeTelemValue = await getTelemValuePromise; - const ladTableValuePromise = await page.waitForSelector(`text="${subscribeTelemValue}"`); - const ladTableValue = await ladTableValuePromise.textContent(); - - expect(ladTableValue).toBe(subscribeTelemValue); + await expect(page.getByLabel('lad value')).toHaveText(subscribeTelemValue); }); }); @@ -338,3 +338,18 @@ async function subscribeToTelemetry(page, objectIdentifier) { return getTelemValuePromise; } + +/** + * Open the given `domainObject`'s context menu from the object tree. + * Expands the path to the object and scrolls to it if necessary. + * + * @param {import('@playwright/test').Page} page + * @param {string} url the url to the object + */ +async function openObjectTreeContextMenu(page, url) { + await page.goto(url); + await page.getByLabel('Show selected item in tree').click(); + await page.locator('.is-navigated-object').click({ + button: 'right' + }); +} diff --git a/e2e/tests/functional/plugins/lad/ladTable.e2e.spec.js b/e2e/tests/functional/plugins/lad/ladTable.e2e.spec.js index 42ca563ffe..10bae36f35 100644 --- a/e2e/tests/functional/plugins/lad/ladTable.e2e.spec.js +++ b/e2e/tests/functional/plugins/lad/ladTable.e2e.spec.js @@ -76,7 +76,7 @@ test.describe('LAD Table', () => { // Right-click the SWG treeitem context menu and click 'Remove' and confirm await page.getByRole('treeitem', { name: swg.name }).click({ button: 'right' }); await page.getByRole('menuitem', { name: 'Remove' }).click(); - await page.getByRole('button', { name: 'OK', exact: true }).click(); + await page.getByRole('button', { name: 'Ok', exact: true }).click(); // Assert that the SWG is no longer in the tree and the table is empty await expect(page.getByRole('treeitem', { name: swg.name })).toBeHidden(); diff --git a/e2e/tests/functional/plugins/notebook/notebook.e2e.spec.js b/e2e/tests/functional/plugins/notebook/notebook.e2e.spec.js index 296cd20f4e..070a99058e 100644 --- a/e2e/tests/functional/plugins/notebook/notebook.e2e.spec.js +++ b/e2e/tests/functional/plugins/notebook/notebook.e2e.spec.js @@ -259,7 +259,7 @@ test.describe('Notebook export tests', () => { const exportedText = await streamToString(readStream); expect(exportedText).toContain('Foo bar entry'); }); - test.fixme('can export multiple notebook entries as text ', async ({ page }) => {}); + test.fixme('can export multiple notebook entries as text', async ({ page }) => {}); test.fixme('can export all notebook entry metdata', async ({ page }) => {}); test.fixme('can export all notebook tags', async ({ page }) => {}); test.fixme('can export all notebook snapshots', async ({ page }) => {}); @@ -296,7 +296,7 @@ test.describe('Notebook entry tests', () => { await expect(page.getByLabel('Notebook Entry Input')).toBeVisible(); await expect(page.getByLabel('Notebook Entry', { exact: true })).toHaveClass(/is-selected/); }); - test('When an object is dropped into a notebook, a new entry is created and it should be focused @unstable', async ({ + test('When an object is dropped into a notebook, a new entry is created and it should be focused', async ({ page }) => { // Create Overlay Plot @@ -320,7 +320,7 @@ test.describe('Notebook entry tests', () => { await expect(embed).toHaveClass(/icon-plot-overlay/); expect(embedName).toBe(overlayPlot.name); }); - test('When an object is dropped into a notebooks existing entry, it should be focused @unstable', async ({ + test('When an object is dropped into a notebooks existing entry, it should be focused', async ({ page }) => { // Create Overlay Plot @@ -354,19 +354,19 @@ test.describe('Notebook entry tests', () => { await page.goto(notebookObject.url); await nbUtils.enterTextEntry(page, 'First Entry'); - await page.hover('text="First Entry"'); - await page.click('button[title="Delete this entry"]'); - await page.getByRole('button', { name: 'Ok' }).filter({ hasText: 'Ok' }).click(); - await expect(page.locator('text="First Entry"')).toBeHidden(); + await page.getByLabel('Notebook Entry', { exact: true }).hover(); + await page.getByLabel('Delete this entry').click(); + await page.getByRole('button', { name: 'Ok', exact: true }).click(); + await expect(page.getByText('First Entry')).toBeHidden(); await nbUtils.enterTextEntry(page, 'Another First Entry'); await nbUtils.enterTextEntry(page, 'Second Entry'); await nbUtils.enterTextEntry(page, 'Third Entry'); - await page.hover('[aria-label="Notebook Entry"] >> nth=2'); - await page.click('button[title="Delete this entry"] >> nth=2'); - await page.getByRole('button', { name: 'Ok' }).filter({ hasText: 'Ok' }).click(); - await expect(page.locator('text="Third Entry"')).toBeHidden(); - await expect(page.locator('text="Another First Entry"')).toBeVisible(); - await expect(page.locator('text="Second Entry"')).toBeVisible(); + await page.getByLabel('Notebook Entry', { exact: true }).nth(2).hover(); + await page.getByLabel('Delete this entry').nth(2).click(); + await page.getByRole('button', { name: 'Ok', exact: true }).click(); + await expect(page.getByText('Third Entry')).toBeHidden(); + await expect(page.getByText('Another First Entry')).toBeVisible(); + await expect(page.getByText('Second Entry')).toBeVisible(); }); test('when a valid link is entered into a notebook entry, it becomes clickable when viewing', async ({ page @@ -383,7 +383,7 @@ test.describe('Notebook entry tests', () => { const validLink = page.locator(`a[href="${TEST_LINK}"]`); - expect(await validLink.count()).toBe(1); + await expect(validLink).toHaveCount(1); // Start waiting for popup before clicking. Note no await. const popupPromise = page.waitForEvent('popup'); @@ -410,7 +410,7 @@ test.describe('Notebook entry tests', () => { const invalidLink = page.locator(`a[href="${TEST_LINK}"]`); - expect(await invalidLink.count()).toBe(0); + await expect(invalidLink).toHaveCount(0); }); test('when a link is entered, but it is not in the whitelisted urls, it does not become clickable when viewing', async ({ page @@ -427,7 +427,7 @@ test.describe('Notebook entry tests', () => { const invalidLink = page.locator(`a[href="${TEST_LINK}"]`); - expect(await invalidLink.count()).toBe(0); + await expect(invalidLink).toHaveCount(0); }); test('when a valid link with a subdomain and a valid domain in the whitelisted urls is entered into a notebook entry, it becomes clickable when viewing', async ({ page @@ -444,7 +444,7 @@ test.describe('Notebook entry tests', () => { const validLink = page.locator(`a[href="${INVALID_TEST_LINK}"]`); - expect(await validLink.count()).toBe(1); + await expect(validLink).toHaveCount(1); }); test('when a valid secure link is entered into a notebook entry, it becomes clickable when viewing', async ({ page @@ -461,7 +461,7 @@ test.describe('Notebook entry tests', () => { const validLink = page.locator(`a[href="${TEST_LINK}"]`); - expect(await validLink.count()).toBe(1); + await expect(validLink).toHaveCount(1); // Start waiting for popup before clicking. Note no await. const popupPromise = page.waitForEvent('popup'); @@ -494,7 +494,7 @@ test.describe('Notebook entry tests', () => { const unsanitizedLink = page.locator(`a[href="${TEST_LINK_BAD}"]`); expect.soft(await sanitizedLink.count()).toBe(1); - expect(await unsanitizedLink.count()).toBe(0); + await expect(unsanitizedLink).toHaveCount(0); }); test('Can add markdown to a notebook entry', async ({ page }) => { await page.goto(notebookObject.url); diff --git a/e2e/tests/functional/plugins/notebook/notebookSnapshotImage.e2e.spec.js b/e2e/tests/functional/plugins/notebook/notebookSnapshotImage.e2e.spec.js index 15fd97b214..8159238e14 100644 --- a/e2e/tests/functional/plugins/notebook/notebookSnapshotImage.e2e.spec.js +++ b/e2e/tests/functional/plugins/notebook/notebookSnapshotImage.e2e.spec.js @@ -76,7 +76,7 @@ test.describe('Snapshot image tests', () => { const secondThumbnail = page.getByRole('img', { name: 'favicon-96x96.png thumbnail' }).nth(1); await secondThumbnail.waitFor({ state: 'attached' }); // expect two embedded images now - expect(await page.getByRole('img', { name: 'favicon-96x96.png thumbnail' }).count()).toBe(2); + await expect(page.getByRole('img', { name: 'favicon-96x96.png thumbnail' })).toHaveCount(2); await page.locator('.c-snapshot.c-ne__embed').first().getByTitle('More actions').click(); @@ -86,7 +86,7 @@ test.describe('Snapshot image tests', () => { await secondThumbnail.waitFor({ state: 'detached' }); // expect one embedded image now as we deleted the other - expect(await page.getByRole('img', { name: 'favicon-96x96.png thumbnail' }).count()).toBe(1); + await expect(page.getByRole('img', { name: 'favicon-96x96.png thumbnail' })).toHaveCount(1); }); }); diff --git a/e2e/tests/functional/plugins/notebook/notebookWithCouchDB.e2e.spec.js b/e2e/tests/functional/plugins/notebook/notebookWithCouchDB.e2e.spec.js index 985af38447..e16117c331 100644 --- a/e2e/tests/functional/plugins/notebook/notebookWithCouchDB.e2e.spec.js +++ b/e2e/tests/functional/plugins/notebook/notebookWithCouchDB.e2e.spec.js @@ -37,7 +37,7 @@ test.describe('Notebook Tests with CouchDB @couchdb', () => { // Create Notebook testNotebook = await createDomainObjectWithDefaults(page, { type: 'Notebook' }); - await page.goto(testNotebook.url, { waitUntil: 'networkidle' }); + await page.goto(testNotebook.url, { waitUntil: 'domcontentloaded' }); }); test('Inspect Notebook Entry Network Requests', async ({ page }) => { @@ -55,15 +55,15 @@ test.describe('Notebook Tests with CouchDB @couchdb', () => { // Waits for the next request with the specified url page.waitForRequest(`**/openmct/${testNotebook.uuid}`), // Triggers the request - page.click('[aria-label="Add Page"]') + page.getByLabel('Add Page').click() ]); // Ensures that there are no other network requests - await page.waitForLoadState('networkidle'); + await page.waitForLoadState('domcontentloaded'); // Assert that only two requests are made // Network Requests are: // 1) The actual POST to create the page - expect(notebookElementsRequests.length).toBe(1); + expect(notebookElementsRequests).toHaveLength(1); // Assert on request object expect(notebookUrlRequest.postDataJSON().metadata.name).toBe(testNotebook.name); @@ -77,7 +77,7 @@ test.describe('Notebook Tests with CouchDB @couchdb', () => { // 2) The shared worker event from 👆 POST request notebookElementsRequests = []; await nbUtils.enterTextEntry(page, 'First Entry'); - await page.waitForLoadState('networkidle'); + await page.waitForLoadState('domcontentloaded'); expect(notebookElementsRequests.length).toBeLessThanOrEqual(2); // Add some tags @@ -120,8 +120,8 @@ test.describe('Notebook Tests with CouchDB @couchdb', () => { expect(filterNonFetchRequests(notebookElementsRequests).length).toBeLessThanOrEqual(12); // Add two more pages - await page.click('[aria-label="Add Page"]'); - await page.click('[aria-label="Add Page"]'); + await page.getByLabel('Add Page').click(); + await page.getByLabel('Add Page').click(); // Add three entries await nbUtils.enterTextEntry(page, 'First Entry'); @@ -141,7 +141,7 @@ test.describe('Notebook Tests with CouchDB @couchdb', () => { // 4) The shared worker event from 👆 POST request notebookElementsRequests = []; await nbUtils.enterTextEntry(page, 'Fourth Entry'); - page.waitForLoadState('networkidle'); + page.waitForLoadState('domcontentloaded'); expect(filterNonFetchRequests(notebookElementsRequests).length).toBeLessThanOrEqual(4); @@ -153,7 +153,7 @@ test.describe('Notebook Tests with CouchDB @couchdb', () => { // 4) The shared worker event from 👆 POST request notebookElementsRequests = []; await nbUtils.enterTextEntry(page, 'Fifth Entry'); - page.waitForLoadState('networkidle'); + page.waitForLoadState('domcontentloaded'); expect(filterNonFetchRequests(notebookElementsRequests).length).toBeLessThanOrEqual(4); @@ -164,7 +164,7 @@ test.describe('Notebook Tests with CouchDB @couchdb', () => { // 4) The shared worker event from 👆 POST request notebookElementsRequests = []; await nbUtils.enterTextEntry(page, 'Sixth Entry'); - page.waitForLoadState('networkidle'); + page.waitForLoadState('domcontentloaded'); expect(filterNonFetchRequests(notebookElementsRequests).length).toBeLessThanOrEqual(4); }); @@ -227,7 +227,7 @@ async function addTagAndAwaitNetwork(page, tagName) { page.locator(`[aria-label="Autocomplete Options"] >> text=${tagName}`).click(), expect(page.locator(`[aria-label="Tag"]:has-text("${tagName}")`)).toBeVisible() ]); - await page.waitForLoadState('networkidle'); + await page.waitForLoadState('domcontentloaded'); } /** @@ -246,5 +246,5 @@ async function removeTagAndAwaitNetwork(page, tagName) { ) ]); await expect(page.locator(`[aria-label="Tag"]:has-text("${tagName}")`)).toBeHidden(); - await page.waitForLoadState('networkidle'); + await page.waitForLoadState('domcontentloaded'); } diff --git a/e2e/tests/functional/plugins/notebook/restrictedNotebook.e2e.spec.js b/e2e/tests/functional/plugins/notebook/restrictedNotebook.e2e.spec.js index 9b66e044f2..d57ebb754d 100644 --- a/e2e/tests/functional/plugins/notebook/restrictedNotebook.e2e.spec.js +++ b/e2e/tests/functional/plugins/notebook/restrictedNotebook.e2e.spec.js @@ -20,7 +20,6 @@ * at runtime from the About dialog for additional information. *****************************************************************************/ -import { openObjectTreeContextMenu } from '../../../../appActions.js'; import { dragAndDropEmbed, enterTextEntry, @@ -51,27 +50,22 @@ test.describe('Restricted Notebook', () => { const restrictedNotebookTreeObject = page.locator(`a:has-text("${notebook.name}")`); // notebook tree object exists - expect.soft(await restrictedNotebookTreeObject.count()).toEqual(1); + await expect(restrictedNotebookTreeObject).toHaveCount(1); // Click Remove Text await page.locator('li[role="menuitem"]:has-text("Remove")').click(); - // Click 'OK' on confirmation window and wait for save banner to appear - await Promise.all([ - page.waitForNavigation(), - page.locator('button:has-text("OK")').click(), - page.waitForSelector('.c-message-banner__message') - ]); + // Click 'Ok' on confirmation window + await page.locator('button:has-text("OK")').click(); // has been deleted - expect(await restrictedNotebookTreeObject.count()).toEqual(0); + await expect(restrictedNotebookTreeObject).toHaveCount(0); }); test('Can be locked if at least one page has one entry @addInit', async ({ page }) => { await enterTextEntry(page, TEST_TEXT); - const commitButton = page.locator('button:has-text("Commit Entries")'); - expect(await commitButton.count()).toEqual(1); + await expect(page.getByLabel('Commit Entries')).toHaveCount(1); }); }); @@ -86,20 +80,18 @@ test.describe('Restricted Notebook with at least one entry and with the page loc await page.locator('button.c-notebook__toggle-nav-button').click(); }); - test('Locked page should now be in a locked state @addInit @unstable', async ({ - page - }, testInfo) => { + test('Locked page should now be in a locked state @addInit', async ({ page }, testInfo) => { // eslint-disable-next-line playwright/no-skipped-test test.skip(testInfo.project === 'chrome-beta', 'Test is unreliable on chrome-beta'); // main lock message on page const lockMessage = page.locator( 'text=This page has been committed and cannot be modified or removed' ); - expect.soft(await lockMessage.count()).toEqual(1); + await expect(lockMessage).toHaveCount(1); // lock icon on page in sidebar const pageLockIcon = page.locator('ul.c-notebook__pages li div.icon-lock'); - expect.soft(await pageLockIcon.count()).toEqual(1); + await expect(pageLockIcon).toHaveCount(1); // no way to remove a restricted notebook with a locked page await openObjectTreeContextMenu(page, notebook.url); @@ -119,17 +111,14 @@ test.describe('Restricted Notebook with at least one entry and with the page loc await page.getByText('Unnamed Page').nth(1).fill(TEST_TEXT_NAME); // expect to be able to rename unlocked pages - const newPageElement = page.getByText(TEST_TEXT_NAME); - const newPageCount = await newPageElement.count(); - await newPageElement.press('Enter'); // exit contenteditable state - expect.soft(newPageCount).toEqual(1); + await page.getByText(TEST_TEXT_NAME).press('Enter'); // exit contenteditable state + await expect(page.locator('div').filter({ hasText: /^Test Page$/ })).toHaveCount(1); // enter test text await enterTextEntry(page, TEST_TEXT); // expect new page to be lockable - const commitButton = page.getByRole('button', { name: ' Commit Entries' }); - expect.soft(await commitButton.count()).toEqual(1); + await expect(page.getByLabel('Commit Entries')).toHaveCount(1); // Click the context menu button for the new page await page.getByTitle('Open context menu').click(); @@ -140,7 +129,7 @@ test.describe('Restricted Notebook with at least one entry and with the page loc // deleted page, should no longer exist const deletedPageElement = page.getByText(TEST_TEXT_NAME); - expect(await deletedPageElement.count()).toEqual(0); + await expect(deletedPageElement).toHaveCount(0); }); }); @@ -173,7 +162,7 @@ test.describe('can export restricted notebook as text', () => { await startAndAddRestrictedNotebookObject(page); }); - test('basic functionality ', async ({ page }) => { + test('basic functionality', async ({ page }) => { await enterTextEntry(page, `Foo bar entry`); // Click on 3 Dot Menu await page.locator('button[title="More actions"]').click(); @@ -190,8 +179,23 @@ test.describe('can export restricted notebook as text', () => { expect(exportedText).toContain('Foo bar entry'); }); - test.fixme('can export multiple notebook entries as text ', async ({ page }) => {}); + test.fixme('can export multiple notebook entries as text', async ({ page }) => {}); test.fixme('can export all notebook entry metdata', async ({ page }) => {}); test.fixme('can export all notebook tags', async ({ page }) => {}); test.fixme('can export all notebook snapshots', async ({ page }) => {}); }); + +/** + * Open the given `domainObject`'s context menu from the object tree. + * Expands the path to the object and scrolls to it if necessary. + * + * @param {import('@playwright/test').Page} page + * @param {string} url the url to the object + */ +async function openObjectTreeContextMenu(page, url) { + await page.goto(url); + await page.getByLabel('Show selected item in tree').click(); + await page.locator('.is-navigated-object').click({ + button: 'right' + }); +} diff --git a/e2e/tests/functional/plugins/plot/autoscale.e2e.spec.js b/e2e/tests/functional/plugins/plot/autoscale.e2e.spec.js index 1cd53295c3..464d5284e6 100644 --- a/e2e/tests/functional/plugins/plot/autoscale.e2e.spec.js +++ b/e2e/tests/functional/plugins/plot/autoscale.e2e.spec.js @@ -24,7 +24,10 @@ Testsuite for plot autoscale. */ -import { createDomainObjectWithDefaults } from '../../../../appActions.js'; +import { + createDomainObjectWithDefaults, + navigateToObjectWithFixedTimeBounds +} from '../../../../appActions.js'; import { expect, test } from '../../../../pluginFixtures.js'; test.use({ viewport: { @@ -51,9 +54,7 @@ test.describe('Autoscale', () => { }); // Switch to fixed time, start: 2022-03-28 22:00:00.000 UTC, end: 2022-03-28 22:00:30.000 UTC - await page.goto( - `${overlayPlot.url}?tc.mode=fixed&tc.startBound=1648591200000&tc.endBound=1648591230000&tc.timeSystem=utc&view=plot-overlay` - ); + await navigateToObjectWithFixedTimeBounds(page, overlayPlot.url, 1648591200000, 1648591230000); await testYTicks(page, ['-1.00', '-0.50', '0.00', '0.50', '1.00']); @@ -69,15 +70,15 @@ test.describe('Autoscale', () => { await page.getByLabel('Y Axis 1 Maximum value').fill('2'); // save - await page.click('button[title="Save"]'); + await page.getByLabel('Save').click(); await Promise.all([ page.getByRole('listitem', { name: 'Save and Finish Editing' }).click(), //Wait for Save Banner to appear - page.waitForSelector('.c-message-banner__message') + page.locator('.c-message-banner__message').hover({ trial: true }) ]); //Wait until Save Banner is gone await page.locator('.c-message-banner__close-button').click(); - await page.waitForSelector('.c-message-banner__message', { state: 'detached' }); + await page.locator('.c-message-banner__message').waitFor({ state: 'detached' }); // Make sure that after turning off autoscale, the user entered range values are reflected in the ticks. await testYTicks(page, [ diff --git a/e2e/tests/functional/plugins/plot/logPlot.e2e.spec.js b/e2e/tests/functional/plugins/plot/logPlot.e2e.spec.js index e327b3d093..3b92bb3933 100644 --- a/e2e/tests/functional/plugins/plot/logPlot.e2e.spec.js +++ b/e2e/tests/functional/plugins/plot/logPlot.e2e.spec.js @@ -29,15 +29,46 @@ import { createDomainObjectWithDefaults, setTimeConductorBounds } from '../../.. import { expect, test } from '../../../../pluginFixtures.js'; test.describe('Log plot tests', () => { - test('Log Plot ticks are functionally correct in regular and log mode and after refresh', async ({ - page, - openmctConfig - }) => { - const { myItemsFolderName } = openmctConfig; - //Test is slow and should be split in the future - test.slow(); + test.beforeEach(async ({ page }) => { + // fresh page with time range from 2022-03-29 22:00:00.000Z to 2022-03-29 22:00:30.000Z + await page.goto('./', { waitUntil: 'domcontentloaded' }); - await makeOverlayPlot(page, myItemsFolderName); + // Set a specific time range for consistency, otherwise it will change + // on every test to a range based on the current time. + const startDate = '2022-03-29'; + const startTime = '22:00:00'; + const endDate = '2022-03-29'; + const endTime = '22:00:30'; + + await setTimeConductorBounds(page, { startDate, startTime, endDate, endTime }); + + const overlayPlot = await createDomainObjectWithDefaults(page, { + type: 'Overlay Plot', + name: 'Unnamed Overlay Plot' + }); + + // create a sinewave generator + await createDomainObjectWithDefaults(page, { + type: 'Sine Wave Generator', + name: 'Unnamed Sine Wave Generator', + parent: overlayPlot.uuid + }); + + await page.getByLabel('More actions').click(); + await page.getByLabel('Edit Properties...').click(); + + // set amplitude to 6, offset 4, data rate 2 hz + await page.getByLabel('Amplitude', { exact: true }).fill('6'); + await page.getByLabel('Offset', { exact: true }).fill('4'); + await page.getByLabel('Data Rate (hz)', { exact: true }).fill('2'); + + await page.getByLabel('Save').click(); + + await page.goto(overlayPlot.url); + }); + test('Log Plot ticks are functionally correct in regular and log mode and after refresh', async ({ + page + }) => { await testRegularTicks(page); await enableEditMode(page); await page.getByRole('tab', { name: 'Config' }).click(); @@ -47,83 +78,35 @@ test.describe('Log plot tests', () => { await testRegularTicks(page); await enableLogMode(page); await testLogTicks(page); - await saveOverlayPlot(page); + await page.getByRole('button', { name: 'Save' }).click(); + await page.getByRole('listitem', { name: 'Save and Finish Editing' }).click(); await testLogTicks(page); }); // Leaving test as 'TODO' for now. // NOTE: Not eligible for community contributions. - test.fixme( - 'Verify that log mode option is reflected in import/export JSON', - async ({ page, openmctConfig }) => { - const { myItemsFolderName } = openmctConfig; + test.fixme('Verify that log mode option is reflected in import/export JSON', async ({ page }) => { + await enableEditMode(page); + await enableLogMode(page); + await page.getByRole('button', { name: 'Save' }).click(); + await page.getByRole('listitem', { name: 'Save and Finish Editing' }).click(); - await makeOverlayPlot(page, myItemsFolderName); - await enableEditMode(page); - await enableLogMode(page); - await saveOverlayPlot(page); + // TODO ...export, delete the overlay, then import it... - // TODO ...export, delete the overlay, then import it... + //await testLogTicks(page); - //await testLogTicks(page); - - // TODO, the plot is slightly at different position that in the other test, so this fails. - // ...We can fix it by copying all steps from the first test... - // await testLogPlotPixels(page); - } - ); + // TODO, the plot is slightly at different position that in the other test, so this fails. + // ...We can fix it by copying all steps from the first test... + // await testLogPlotPixels(page); + }); }); -/** - * Makes an overlay plot with a sine wave generator and clicks on the overlay plot in the sidebar so it is the active thing displayed. - * @param {import('@playwright/test').Page} page - * @param {string} myItemsFolderName - */ -async function makeOverlayPlot(page, myItemsFolderName) { - // fresh page with time range from 2022-03-29 22:00:00.000Z to 2022-03-29 22:00:30.000Z - await page.goto('./', { waitUntil: 'domcontentloaded' }); - - // Set a specific time range for consistency, otherwise it will change - // on every test to a range based on the current time. - - const startDate = '2022-03-29'; - const startTime = '22:00:00'; - const endDate = '2022-03-29'; - const endTime = '22:00:30'; - - await setTimeConductorBounds(page, { startDate, startTime, endDate, endTime }); - - const overlayPlot = await createDomainObjectWithDefaults(page, { - type: 'Overlay Plot', - name: 'Unnamed Overlay Plot' - }); - - // create a sinewave generator - await createDomainObjectWithDefaults(page, { - type: 'Sine Wave Generator', - name: 'Unnamed Sine Wave Generator', - parent: overlayPlot.uuid - }); - - await page.getByLabel('More actions').click(); - await page.getByLabel('Edit Properties...').click(); - - // set amplitude to 6, offset 4, data rate 2 hz - await page.getByLabel('Amplitude', { exact: true }).fill('6'); - await page.getByLabel('Offset', { exact: true }).fill('4'); - await page.getByLabel('Data Rate (hz)', { exact: true }).fill('2'); - - await page.getByLabel('Save').click(); - - await page.goto(overlayPlot.url); -} - /** * @param {import('@playwright/test').Page} page */ async function testRegularTicks(page) { const yTicks = page.locator('.gl-plot-y-tick-label'); - expect(await yTicks.count()).toBe(7); + await expect(yTicks).toHaveCount(7); await expect(yTicks.nth(0)).toHaveText('-2'); await expect(yTicks.nth(1)).toHaveText('0'); await expect(yTicks.nth(2)).toHaveText('2'); @@ -138,7 +121,7 @@ async function testRegularTicks(page) { */ async function testLogTicks(page) { const yTicks = page.locator('.gl-plot-y-tick-label'); - expect(await yTicks.count()).toBe(9); + await expect(yTicks).toHaveCount(9); await expect(yTicks.nth(0)).toHaveText('-2.98'); await expect(yTicks.nth(1)).toHaveText('-1.51'); await expect(yTicks.nth(2)).toHaveText('-0.58'); @@ -175,26 +158,6 @@ async function disableLogMode(page) { await page.getByRole('checkbox', { name: 'Log mode' }).uncheck(); } -/** - * @param {import('@playwright/test').Page} page - */ -async function saveOverlayPlot(page) { - // save overlay plot - await page - .locator('text=Snapshot Save and Finish Editing Save and Continue Editing >> button') - .nth(1) - .click(); - - await Promise.all([ - page.getByRole('listitem', { name: 'Save and Finish Editing' }).click(), - //Wait for Save Banner to appear - 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' }); -} - /** * @param {import('@playwright/test').Page} page */ diff --git a/e2e/tests/functional/plugins/plot/overlayPlot.e2e.spec.js b/e2e/tests/functional/plugins/plot/overlayPlot.e2e.spec.js index c5a9aa6874..7af05a00ea 100644 --- a/e2e/tests/functional/plugins/plot/overlayPlot.e2e.spec.js +++ b/e2e/tests/functional/plugins/plot/overlayPlot.e2e.spec.js @@ -52,8 +52,8 @@ test.describe('Overlay Plot', () => { await page.getByRole('tab', { name: 'Config' }).click(); // navigate to plot series color palette - await page.click('.l-browse-bar__actions__edit'); - await page.locator('li.c-tree__item.menus-to-left .c-disclosure-triangle').click(); + await page.getByLabel('Edit Object').click(); + await page.getByLabel('Expand Sine Wave Generator:').click(); await page.locator('.c-click-swatch--menu').click(); await page.locator('.c-palette__item[style="background: rgb(255, 166, 61);"]').click(); // gets color for swatch located in legend @@ -93,7 +93,7 @@ test.describe('Overlay Plot', () => { await expect(page.getByLabel('Plot Legend Expanded')).toBeHidden(); await expect(page.getByLabel('Expand by Default')).toHaveText(/No/); - expect(await page.getByLabel('Plot Legend Item').count()).toBe(3); + await expect(page.getByLabel('Plot Legend Item')).toHaveCount(3); // Change the legend to expand by default await page.getByLabel('Edit Object').click(); @@ -136,8 +136,8 @@ test.describe('Overlay Plot', () => { 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); + await page.locator('.js-limit-area').waitFor({ state: 'attached' }); + await expect(page.locator('.c-plot-limit-line')).toHaveCount(0); // Enter edit mode await page.getByLabel('Edit Object').click(); @@ -200,8 +200,8 @@ test.describe('Overlay Plot', () => { 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); + await expect(page.locator('.js-limit-area')).toBeAttached(); + await expect(page.locator('.c-plot-limit-line')).toHaveCount(0); // Enter edit mode await page.getByLabel('Edit Object').click(); @@ -309,32 +309,26 @@ test.describe('Overlay Plot', () => { expect(yAxis3Group.getByRole('listitem').nth(0).getByText(swgB.name)).toBeTruthy(); }); - test.fixme( - 'Clicking on an item in the elements pool brings up the plot preview with data points', - async ({ page }) => { - test.info().annotations.push({ - type: 'issue', - description: 'https://github.com/nasa/openmct/issues/7421' - }); + test('Clicking on an item in the elements pool brings up the plot preview with data points', async ({ + page + }) => { + 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); + // Wait for plot series data to load and be drawn + await waitForPlotsToRender(page); + await page.getByLabel('Edit Object').click(); - await page.goto(overlayPlot.url); - // Wait for plot series data to load and be drawn - await waitForPlotsToRender(page); - await page.getByLabel('Edit Object').click(); + await page.getByRole('tab', { name: 'Elements' }).click(); - await page.getByRole('tab', { name: 'Elements' }).click(); - - await page.locator(`#inspector-elements-tree >> text=${swgA.name}`).click(); - const plotPixels = await getCanvasPixels(page, '.js-overlay canvas'); - const plotPixelSize = plotPixels.length; - expect(plotPixelSize).toBeGreaterThan(0); - } - ); + await page.locator(`#inspector-elements-tree >> text=${swgA.name}`).click(); + const plotPixels = await getCanvasPixels(page, '.js-overlay canvas'); + const plotPixelSize = plotPixels.length; + expect(plotPixelSize).toBeGreaterThan(0); + }); test('Can remove an item via the elements pool action menu', async ({ page }) => { const swgA = await createDomainObjectWithDefaults(page, { @@ -357,7 +351,7 @@ test.describe('Overlay Plot', () => { await expect(swgAElementsPoolItem).toBeVisible(); await swgAElementsPoolItem.click({ button: 'right' }); await page.getByRole('menuitem', { name: 'Remove' }).click(); - await page.getByRole('button', { name: 'OK', exact: true }).click(); + await page.getByRole('button', { name: 'Ok', exact: true }).click(); await expect(swgAElementsPoolItem).toBeHidden(); await page.getByRole('button', { name: 'Save' }).click(); @@ -387,7 +381,7 @@ async function assertLimitLinesExistAndAreVisible(page) { // Wait for plot series data to load await waitForPlotsToRender(page); // Wait for limit lines to be created - await page.waitForSelector('.js-limit-area', { state: 'attached' }); + await page.locator('.js-limit-area').waitFor({ state: 'attached' }); // There should be 10 limit lines created by default await expect(page.locator('.c-plot-limit-line')).toHaveCount(10); const limitLineCount = await page.locator('.c-plot-limit-line').count(); diff --git a/e2e/tests/functional/plugins/plot/plotControls.e2e.spec.js b/e2e/tests/functional/plugins/plot/plotControls.e2e.spec.js index ba614cf90c..87f608f528 100644 --- a/e2e/tests/functional/plugins/plot/plotControls.e2e.spec.js +++ b/e2e/tests/functional/plugins/plot/plotControls.e2e.spec.js @@ -85,15 +85,8 @@ test.describe('Plot Controls', () => { await page.getByLabel('Y Axis 1 Maximum value').fill('1'); // save - await page.click('button[title="Save"]'); - await Promise.all([ - page.getByRole('listitem', { name: 'Save and Finish Editing' }).click(), - //Wait for Save Banner to appear - 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' }); + await page.getByRole('button', { name: 'Save' }).click(); + await page.getByRole('listitem', { name: 'Save and Finish Editing' }).click(); // hover over plot for plot controls await page.getByLabel('Plot Canvas').hover(); // click on pause control diff --git a/e2e/tests/functional/plugins/plot/plotRendering.e2e.spec.js b/e2e/tests/functional/plugins/plot/plotRendering.e2e.spec.js index d178b79b8b..d90310d22c 100644 --- a/e2e/tests/functional/plugins/plot/plotRendering.e2e.spec.js +++ b/e2e/tests/functional/plugins/plot/plotRendering.e2e.spec.js @@ -78,17 +78,13 @@ test.describe('Plot Rendering', () => { // click on synchronize with time conductor await page.getByTitle('Synchronize Time Conductor').click(); - await page.getByRole('button', { name: 'OK', exact: true }).click(); + await page.getByRole('button', { name: 'Ok', exact: true }).click(); //confirm that you're now in fixed mode with the correct range await expect(page.getByLabel('Time Conductor Mode')).toHaveText('Fixed Timespan'); }); - test.fixme('Plot is rendered when infinity values exist', async ({ page }) => { - test.info().annotations.push({ - type: 'issue', - description: 'https://github.com/nasa/openmct/issues/7421' - }); + test('Plot is rendered when infinity values exist', async ({ page }) => { // Edit Plot await editSineWaveToUseInfinityOption(page, sineWaveGeneratorObject); diff --git a/e2e/tests/functional/plugins/plot/scatterPlot.e2e.spec.js b/e2e/tests/functional/plugins/plot/scatterPlot.e2e.spec.js index c392c26095..ed25ff5747 100644 --- a/e2e/tests/functional/plugins/plot/scatterPlot.e2e.spec.js +++ b/e2e/tests/functional/plugins/plot/scatterPlot.e2e.spec.js @@ -53,7 +53,7 @@ test.describe('Scatter Plot', () => { await page.goto(scatterPlot.url); await page.getByLabel('Edit Object').click(); await page.getByRole('tab', { name: 'Elements' }).click(); - await expect.soft(page.locator(`#inspector-elements-tree >> text=${swg1.name}`)).toBeVisible(); + await expect(page.getByLabel(`Preview ${swg1.name}`)).toBeVisible(); await page.getByRole('button', { name: 'Save' }).click(); await page.getByRole('listitem', { name: 'Save and Finish Editing' }).click(); @@ -65,14 +65,12 @@ test.describe('Scatter Plot', () => { }); // Verify that the 'Replace telemetry source' modal appears and accept it - await expect - .soft( - page.locator( - 'text=This action will replace the current telemetry source. Do you want to continue?' - ) + await expect( + page.getByText( + 'This action will replace the current telemetry source. Do you want to continue?' ) - .toBeVisible(); - await page.click('text=Ok'); + ).toBeVisible(); + await page.getByRole('button', { name: 'Ok', exact: true }).click(); // Navigate to the scatter plot and verify that the new SWG // appears in the elements pool and the old one is gone @@ -81,27 +79,25 @@ test.describe('Scatter Plot', () => { // Click the "Elements" tab await page.getByRole('tab', { name: 'Elements' }).click(); - await expect.soft(page.locator(`#inspector-elements-tree >> text=${swg1.name}`)).toBeHidden(); - await expect.soft(page.locator(`#inspector-elements-tree >> text=${swg2.name}`)).toBeVisible(); + await expect(page.getByLabel(`Preview ${swg1.name}`)).toBeHidden(); + await expect(page.getByLabel(`Preview ${swg2.name}`)).toBeVisible(); await page.getByRole('button', { name: 'Save' }).click(); // Right click on the new SWG in the elements pool and delete it - await page.locator(`#inspector-elements-tree >> text=${swg2.name}`).click({ + await page.getByLabel(`Preview ${swg2.name}`).click({ button: 'right' }); - await page.locator('li[title="Remove this object from its containing object."]').click(); + await page.getByLabel('Remove').click(); // Verify that the 'Remove object' confirmation modal appears and accept it - await expect - .soft( - page.locator( - 'text=Warning! This action will remove this object. Are you sure you want to continue?' - ) + await expect( + page.getByText( + 'Warning! This action will remove this object. Are you sure you want to continue?' ) - .toBeVisible(); - await page.click('text=Ok'); + ).toBeVisible(); + await page.getByRole('button', { name: 'Ok', exact: true }).click(); // Verify that the elements pool shows no elements - await expect(page.locator('text="No contained elements"')).toBeVisible(); + await expect(page.getByText('No contained elements')).toBeVisible(); }); }); diff --git a/e2e/tests/functional/plugins/plot/stackedPlot.e2e.spec.js b/e2e/tests/functional/plugins/plot/stackedPlot.e2e.spec.js index aed64e7e8c..6926097c8f 100644 --- a/e2e/tests/functional/plugins/plot/stackedPlot.e2e.spec.js +++ b/e2e/tests/functional/plugins/plot/stackedPlot.e2e.spec.js @@ -82,7 +82,7 @@ test.describe('Stacked Plot', () => { .getByRole('menuitem') .filter({ hasText: /Remove/ }) .click(); - await page.getByRole('button').filter({ hasText: 'OK' }).click(); + await page.getByRole('button').filter({ hasText: 'Ok' }).click(); await expect(page.locator('#inspector-elements-tree .js-elements-pool__item')).toHaveCount(2); @@ -239,7 +239,7 @@ test.describe('Stacked Plot', () => { await page.getByLabel(`Stacked Plot Item ${swgA.name}`).click(); // Expand config for the series - await page.getByLabel('Expand Sine Wave Generator').click(); + await page.getByLabel('Expand Sine Wave Generator A Plot Series Options').click(); // turn off alarm markers await page.getByLabel('Alarm Markers').uncheck(); @@ -256,8 +256,7 @@ test.describe('Stacked Plot', () => { await page.getByLabel(`Stacked Plot Item ${swgA.name}`).click(); // Expand config for the series - //TODO Fix this locator - await page.getByLabel('Expand Sine Wave Generator A generator').click(); + await page.getByLabel('Expand Sine Wave Generator A Plot Series Options').click(); // Assert that alarm markers are still turned off await expect( @@ -277,7 +276,7 @@ test.describe('Stacked Plot', () => { await page.getByRole('tab', { name: 'Config' }).click(); - let legendProperties = await page.locator('[aria-label="Legend Properties"]'); + const legendProperties = page.getByLabel('Legend Properties'); await legendProperties.locator('[title="Display legends per sub plot."]~div input').uncheck(); await assertAggregateLegendIsVisible(page); @@ -356,11 +355,9 @@ async function assertAggregateLegendIsVisible(page) { // Wait for plot series data to load await waitForPlotsToRender(page); // Wait for plot legend to be shown - await page.waitForSelector('.js-stacked-plot-legend', { state: 'attached' }); + await expect(page.locator('.js-stacked-plot-legend')).toBeVisible(); // There should be 3 legend items - expect( - await page - .locator('.js-stacked-plot-legend .c-plot-legend__wrapper div.plot-legend-item') - .count() - ).toBe(3); + await expect( + page.locator('.js-stacked-plot-legend .c-plot-legend__wrapper div.plot-legend-item') + ).toHaveCount(3); } diff --git a/e2e/tests/functional/plugins/plot/tagging.e2e.spec.js b/e2e/tests/functional/plugins/plot/tagging.e2e.spec.js index 51c2bbbd1f..e62c65772a 100644 --- a/e2e/tests/functional/plugins/plot/tagging.e2e.spec.js +++ b/e2e/tests/functional/plugins/plot/tagging.e2e.spec.js @@ -89,7 +89,7 @@ test.describe('Plot Tagging', () => { await setRealTimeMode(page); // Search for Science Tag - await page.getByRole('searchbox', { name: 'Search Input' }); + await page.getByRole('searchbox', { name: 'Search Input' }).click(); await page.getByRole('searchbox', { name: 'Search Input' }).fill('sc'); // Click on the search object result diff --git a/e2e/tests/functional/plugins/reloadAction/reloadAction.e2e.spec.js b/e2e/tests/functional/plugins/reloadAction/reloadAction.e2e.spec.js index 0855d2efc8..499454bb2a 100644 --- a/e2e/tests/functional/plugins/reloadAction/reloadAction.e2e.spec.js +++ b/e2e/tests/functional/plugins/reloadAction/reloadAction.e2e.spec.js @@ -42,19 +42,21 @@ test.describe('Reload action', () => { await createDomainObjectWithDefaults(page, { type: 'Sine Wave Generator', - parent: alphaTable.uuid, - customParameters: { - '[aria-label="Data Rate (hz)"]': '0.001' - } + parent: alphaTable.uuid }); + await page.getByLabel('More actions').click(); + await page.getByRole('menuitem', { name: /Edit Properties/ }).click(); + await page.getByLabel('Data Rate (hz)', { exact: true }).fill('0.001'); + await page.getByLabel('Save').click(); await createDomainObjectWithDefaults(page, { type: 'Sine Wave Generator', - parent: betaTable.uuid, - customParameters: { - '[aria-label="Data Rate (hz)"]': '0.001' - } + parent: betaTable.uuid }); + await page.getByLabel('More actions').click(); + await page.getByRole('menuitem', { name: /Edit Properties/ }).click(); + await page.getByLabel('Data Rate (hz)', { exact: true }).fill('0.001'); + await page.getByLabel('Save').click(); await page.goto(displayLayout.url); @@ -63,20 +65,26 @@ test.describe('Reload action', () => { 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 + .getByLabel('Main Tree') + .getByLabel(`Preview ${alphaTable.name}`) + .dragTo(page.getByLabel('Layout Grid'), { + targetPosition: { x: 0, y: 0 } + }); - await page.dragAndDrop(`text='Beta Table'`, '.l-layout__grid-holder', { - targetPosition: { x: 0, y: 250 } - }); + await page + .getByLabel('Main Tree') + .getByLabel(`Preview ${betaTable.name}`) + .dragTo(page.getByLabel('Layout Grid'), { + targetPosition: { x: 0, y: 250 } + }); - await page.locator('button[title="Save"]').click(); + await page.getByLabel('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 + const beforeReloadAlphaTelemetryValue = page .getByLabel('Alpha Table table content') .getByLabel('wavelengths table cell') .first() diff --git a/e2e/tests/functional/plugins/styling/flexLayoutStyling.e2e.spec.js b/e2e/tests/functional/plugins/styling/flexLayoutStyling.e2e.spec.js index 27129822fd..e74accfe82 100644 --- a/e2e/tests/functional/plugins/styling/flexLayoutStyling.e2e.spec.js +++ b/e2e/tests/functional/plugins/styling/flexLayoutStyling.e2e.spec.js @@ -466,7 +466,7 @@ test.describe('Flexible Layout styling', () => { page.getByLabel('Flexible Layout Column') ); await page.getByLabel('Cancel Editing').click(); - await page.getByRole('button', { name: 'OK', exact: true }).click(); + await page.getByRole('button', { name: 'Ok', exact: true }).click(); await checkStyles( hexToRGB(defaultBorderTargetColor), NO_STYLE_RGBA, diff --git a/e2e/tests/functional/plugins/tabs/tabs.e2e.spec.js b/e2e/tests/functional/plugins/tabs/tabs.e2e.spec.js index 508a08a75b..8e9a3bb586 100644 --- a/e2e/tests/functional/plugins/tabs/tabs.e2e.spec.js +++ b/e2e/tests/functional/plugins/tabs/tabs.e2e.spec.js @@ -114,8 +114,8 @@ test.describe('Tabs View CRUD', () => { await page.getByLabel('Edit Object').click(); await page.getByLabel('More actions').click(); await page.getByLabel('Edit Properties...').click(); - await expect(await page.getByLabel('Eager Load Tabs')).not.toBeChecked(); + await expect(page.getByLabel('Eager Load Tabs')).not.toBeChecked(); await page.getByLabel('Eager Load Tabs').setChecked(true); - await expect(await page.getByLabel('Eager Load Tabs')).toBeChecked(); + await expect(page.getByLabel('Eager Load Tabs')).toBeChecked(); }); }); diff --git a/e2e/tests/functional/plugins/telemetryTable/telemetryTable.e2e.spec.js b/e2e/tests/functional/plugins/telemetryTable/telemetryTable.e2e.spec.js index 3c94b06952..9aea26f395 100644 --- a/e2e/tests/functional/plugins/telemetryTable/telemetryTable.e2e.spec.js +++ b/e2e/tests/functional/plugins/telemetryTable/telemetryTable.e2e.spec.js @@ -155,8 +155,7 @@ test.describe('Telemetry Table', () => { expect(cells.length).toBeGreaterThan(1); // ensure the text content of each cell contains the search term for (const cell of cells) { - const text = await cell.textContent(); - expect(text).toContain('Roger'); + await expect(cell).toHaveText(/Roger/); } await page.getByRole('searchbox', { name: 'message filter input' }).click(); @@ -167,15 +166,14 @@ test.describe('Telemetry Table', () => { .getByText(/Dodger/) .all(); // ensure we've got more than one cell - expect(cells.length).toBe(0); + expect(cells).toHaveLength(0); // ensure the text content of each cell contains the search term for (const cell of cells) { - const text = await cell.textContent(); - expect(text).not.toContain('Dodger'); + await expect(cell).not.toHaveText(/Dodger/); } // Click pause button - await page.click('button[title="Pause"]'); + await page.getByLabel('Pause').click(); }); test('Supports filtering using Regex', async ({ page }) => { @@ -201,8 +199,7 @@ test.describe('Telemetry Table', () => { expect(cells.length).toBeGreaterThan(1); // ensure the text content of each cell contains the search term for (const cell of cells) { - const text = await cell.textContent(); - expect(text).toContain('Roger'); + await expect(cell).toHaveText(/Roger/); } await page.getByRole('searchbox', { name: 'message filter input' }).click(); @@ -213,15 +210,14 @@ test.describe('Telemetry Table', () => { .getByText(/Dodger/) .all(); // ensure we've got more than one cell - expect(cells.length).toBe(0); + expect(cells).toHaveLength(0); // ensure the text content of each cell contains the search term for (const cell of cells) { - const text = await cell.textContent(); - expect(text).not.toContain('Dodger'); + await expect(cell).not.toHaveText(/Dodger/); } // Click pause button - await page.click('button[title="Pause"]'); + await page.getByLabel('Pause').click(); }); }); @@ -256,7 +252,6 @@ async function getScrollPosition(page, top = true) { scrollHeight: node.scrollHeight })); - // eslint-disable-next-line playwright/no-conditional-in-test if (top) { return scrollTop; } else { diff --git a/e2e/tests/functional/plugins/timeConductor/datepicker.e2e.spec.js b/e2e/tests/functional/plugins/timeConductor/datepicker.e2e.spec.js index e65de8c465..0da324bb98 100644 --- a/e2e/tests/functional/plugins/timeConductor/datepicker.e2e.spec.js +++ b/e2e/tests/functional/plugins/timeConductor/datepicker.e2e.spec.js @@ -22,16 +22,18 @@ import { createDomainObjectWithDefaults, - setIndependentTimeConductorBounds + navigateToObjectWithFixedTimeBounds, + setFixedIndependentTimeConductorBounds } from '../../../../appActions.js'; import { expect, test } from '../../../../pluginFixtures.js'; -const FIXED_TIME = - './#/browse/mine?tc.mode=fixed&tc.startBound=1693592063607&tc.endBound=1693593893607&tc.timeSystem=utc&view=grid&hideInspector=true&hideTree=true'; +const FIXED_TIME_URL = './#/browse/mine'; + test.describe('Datepicker operations', () => { test.beforeEach(async ({ page }) => { - await page.goto(FIXED_TIME); + await navigateToObjectWithFixedTimeBounds(page, FIXED_TIME_URL, 1693592063607, 1693593893607); }); + test('Verify that user can use the datepicker in the TC', async ({ page }) => { await page.getByLabel('Time Conductor Mode').click(); // Click on the date picker that is left-most on the screen @@ -42,12 +44,13 @@ test.describe('Datepicker operations', () => { // Expect datepicker to close and time conductor date setting to be changed await expect(page.getByRole('dialog')).toHaveCount(0); }); + test('Verify that user can use the datepicker in the ITC', async ({ page }) => { const createdTimeList = await createDomainObjectWithDefaults(page, { type: 'Time List' }); await page.goto(createdTimeList.url, { waitUntil: 'domcontentloaded' }); - await setIndependentTimeConductorBounds(page, { + await setFixedIndependentTimeConductorBounds(page, { start: '2024-11-12 19:11:11.000Z', end: '2024-11-12 20:11:11.000Z' }); diff --git a/e2e/tests/functional/plugins/timeConductor/timeConductor.e2e.spec.js b/e2e/tests/functional/plugins/timeConductor/timeConductor.e2e.spec.js index a873b7c306..79346f382e 100644 --- a/e2e/tests/functional/plugins/timeConductor/timeConductor.e2e.spec.js +++ b/e2e/tests/functional/plugins/timeConductor/timeConductor.e2e.spec.js @@ -281,29 +281,3 @@ test.describe('Global Time Conductor', () => { // select an option and verify the offsets are updated correctly }); }); - -test.describe('Time Conductor History', () => { - test('shows milliseconds on hover @unstable', async ({ page }) => { - test.info().annotations.push({ - type: 'issue', - description: 'https://github.com/nasa/openmct/issues/4386' - }); - // Navigate to Open MCT in Fixed Time Mode, UTC Time System - // with startBound at 2022-01-01 00:00:00.000Z - // and endBound at 2022-01-01 00:00:00.200Z - await page.goto( - './#/browse/mine?view=grid&tc.mode=fixed&tc.startBound=1640995200000&tc.endBound=1640995200200&tc.timeSystem=utc&hideInspector=true' - ); - await page.getByRole('button', { name: 'Time Conductor Settings' }).click(); - await page.getByRole('button', { name: 'Time Conductor History' }).hover({ trial: true }); - await page.getByRole('button', { name: 'Time Conductor History' }).click(); - - // Validate history item format - const historyItem = page.locator('text="2022-01-01 00:00:00 + 200ms"'); - await expect(historyItem).toBeEnabled(); - await expect(historyItem).toHaveAttribute( - 'title', - '2022-01-01 00:00:00.000 - 2022-01-01 00:00:00.200' - ); - }); -}); diff --git a/e2e/tests/functional/plugins/timer/timer.e2e.spec.js b/e2e/tests/functional/plugins/timer/timer.e2e.spec.js index 1e0393545f..f63cd4c1e7 100644 --- a/e2e/tests/functional/plugins/timer/timer.e2e.spec.js +++ b/e2e/tests/functional/plugins/timer/timer.e2e.spec.js @@ -20,10 +20,7 @@ * at runtime from the About dialog for additional information. *****************************************************************************/ -import { - createDomainObjectWithDefaults, - openObjectTreeContextMenu -} from '../../../../appActions.js'; +import { createDomainObjectWithDefaults } from '../../../../appActions.js'; import { MISSION_TIME } from '../../../../constants.js'; import { expect, test } from '../../../../pluginFixtures.js'; @@ -33,7 +30,6 @@ test.describe('Timer', () => { test.beforeEach(async ({ page }) => { await page.goto('./', { waitUntil: 'domcontentloaded' }); timer = await createDomainObjectWithDefaults(page, { type: 'timer' }); - await assertTimerElements(page, timer); }); test('Can perform actions on the Timer', async ({ page }) => { @@ -42,13 +38,11 @@ test.describe('Timer', () => { description: 'https://github.com/nasa/openmct/issues/4313' }); - const timerUrl = timer.url; - await test.step('From the tree context menu', async () => { - await triggerTimerContextMenuAction(page, timerUrl, 'Start'); - await triggerTimerContextMenuAction(page, timerUrl, 'Pause'); - await triggerTimerContextMenuAction(page, timerUrl, 'Restart at 0'); - await triggerTimerContextMenuAction(page, timerUrl, 'Stop'); + await triggerTimerContextMenuAction(page, timer.url, 'Start'); + await triggerTimerContextMenuAction(page, timer.url, 'Pause'); + await triggerTimerContextMenuAction(page, timer.url, 'Restart at 0'); + await triggerTimerContextMenuAction(page, timer.url, 'Stop'); }); await test.step('From the 3dot menu', async () => { @@ -67,26 +61,18 @@ test.describe('Timer', () => { }); test.describe('Timer with target date @clock', () => { - let timer; - test.beforeEach(async ({ page }) => { + await page.clock.install({ time: MISSION_TIME }); + await page.clock.resume(); await page.goto('./', { waitUntil: 'domcontentloaded' }); - timer = await createDomainObjectWithDefaults(page, { type: 'timer' }); - await assertTimerElements(page, timer); - }); - - // Override clock - test.use({ - clockOptions: { - now: MISSION_TIME, - shouldAdvanceTime: true - } + await createDomainObjectWithDefaults(page, { type: 'timer' }); }); test('Can count down to a target date', async ({ page }) => { // Set the target date to 2024-11-24 03:30:00 await page.getByTitle('More actions').click(); await page.getByRole('menuitem', { name: /Edit Properties.../ }).click(); + await page.getByPlaceholder('YYYY-MM-DD').fill('2024-11-24'); await page.locator('input[name="hour"]').fill('3'); await page.locator('input[name="min"]').fill('30'); @@ -159,14 +145,13 @@ async function triggerTimerContextMenuAction(page, timerUrl, action) { */ async function triggerTimer3dotMenuAction(page, action) { const menuAction = `.c-menu ul li >> text="${action}"`; - const threeDotMenuButton = 'button[title="More actions"]'; let isActionAvailable = false; let iterations = 0; // Dismiss/open the 3dot menu until the action is available // or a maximum number of iterations is reached while (!isActionAvailable && iterations <= 20) { - await page.click('.c-object-view'); - await page.click(threeDotMenuButton); + await page.getByLabel('Object View').click(); + await page.getByLabel('More actions').click(); isActionAvailable = await page.locator(menuAction).isVisible(); iterations++; } @@ -183,7 +168,7 @@ async function triggerTimer3dotMenuAction(page, action) { async function triggerTimerViewAction(page, action) { await page.locator('.c-timer').hover({ trial: true }); const buttonTitle = buttonTitleFromAction(action); - await page.click(`button[title="${buttonTitle}"]`); + await page.getByLabel(buttonTitle, { exact: true }).click(); assertTimerStateAfterAction(page, action); } @@ -214,11 +199,11 @@ async function assertTimerStateAfterAction(page, action) { case 'Start': case 'Restart at 0': timerStateClass = 'is-started'; - expect(await timerValue.innerText()).toBe('0D 00:00:00'); + await expect(timerValue).toHaveText('0D 00:00:00'); break; case 'Stop': timerStateClass = 'is-stopped'; - expect(await timerValue.innerText()).toBe('--:--:--'); + await expect(timerValue).toHaveText('--:--:--'); break; case 'Pause': timerStateClass = 'is-paused'; @@ -229,23 +214,16 @@ async function assertTimerStateAfterAction(page, action) { } /** - * Assert that all the major components of a timer are present in the DOM. + * Open the given `domainObject`'s context menu from the object tree. + * Expands the path to the object and scrolls to it if necessary. + * * @param {import('@playwright/test').Page} page - * @param {import('../../../../appActions').CreatedObjectInfo} timer + * @param {string} url the url to the object */ -async function assertTimerElements(page, timer) { - const timerElement = page.locator('.c-timer'); - const resetButton = page.getByRole('button', { name: 'Reset' }); - const pausePlayButton = page - .getByRole('button', { name: 'Pause' }) - .or(page.getByRole('button', { name: 'Start' })); - const timerDirectionIcon = page.locator('.c-timer__direction'); - const timerValue = page.locator('.c-timer__value'); - - expect(await page.locator('.l-browse-bar__object-name').innerText()).toBe(timer.name); - expect(timerElement).toBeAttached(); - expect(resetButton).toBeAttached(); - expect(pausePlayButton).toBeAttached(); - expect(timerDirectionIcon).toBeAttached(); - expect(timerValue).toBeAttached(); +async function openObjectTreeContextMenu(page, url) { + await page.goto(url); + await page.getByLabel('Show selected item in tree').click(); + await page.locator('.is-navigated-object').click({ + button: 'right' + }); } diff --git a/e2e/tests/functional/recentObjects.e2e.spec.js b/e2e/tests/functional/recentObjects.e2e.spec.js index 6b18d6eb02..1a5cc7afd7 100644 --- a/e2e/tests/functional/recentObjects.e2e.spec.js +++ b/e2e/tests/functional/recentObjects.e2e.spec.js @@ -95,7 +95,7 @@ test.describe('Recent Objects', () => { ).toBeGreaterThan(0); expect(recentObjectsList.getByRole('listitem', { name: folderA.name })).toBeTruthy(); - await page.click('button[title="Show selected item in tree"]'); + await page.getByLabel('Show selected item in tree').click(); // Delete the folder via the left tree pane treeitem context menu await page .getByRole('treeitem', { name: new RegExp(folderA.name) }) @@ -104,7 +104,7 @@ test.describe('Recent Objects', () => { button: 'right' }); await page.getByRole('menuitem', { name: /Remove/ }).click(); - await page.getByRole('button', { name: 'OK', exact: true }).click(); + await page.getByRole('button', { name: 'Ok', exact: true }).click(); // Verify that the folder and clock are no longer in the recent objects list await expect(recentObjectsList.getByRole('listitem', { name: folderA.name })).toBeHidden(); @@ -227,24 +227,22 @@ test.describe('Recent Objects', () => { .click(); // Assert that two recent objects are displayed and one of them is an alias - expect(await recentObjectsList.getByRole('listitem', { name: clock.name }).count()).toBe(2); - expect(await recentObjectsList.locator('.is-alias').count()).toBe(1); + await expect(recentObjectsList.getByRole('listitem', { name: clock.name })).toHaveCount(2); + await expect(recentObjectsList.locator('.is-alias')).toHaveCount(1); // Assert that the alias and the original's breadcrumbs are different const clockBreadcrumbs = recentObjectsList .getByRole('listitem', { name: clock.name }) .getByRole('navigation'); - expect(await clockBreadcrumbs.count()).toBe(2); - expect(await clockBreadcrumbs.nth(0).innerText()).not.toEqual( - await clockBreadcrumbs.nth(1).innerText() - ); + await expect(clockBreadcrumbs).toHaveCount(2); + await expect(clockBreadcrumbs.nth(0)).not.toHaveText(await clockBreadcrumbs.nth(1).innerText()); }); test('Enforces a limit of 20 recent objects and clears the recent objects', async ({ page }) => { // Creating 21 objects takes a while, so increase the timeout test.slow(); // Assert that the list initially contains 3 objects (clock, folder, my items) - expect(await recentObjectsList.locator('.c-recentobjects-listitem').count()).toBe(3); + await expect(recentObjectsList.locator('.c-recentobjects-listitem')).toHaveCount(3); let lastFolder; let lastClock; @@ -261,7 +259,7 @@ test.describe('Recent Objects', () => { } // Assert that the list contains 20 objects - expect(await recentObjectsList.locator('.c-recentobjects-listitem').count()).toBe(20); + await expect(recentObjectsList.locator('.c-recentobjects-listitem')).toHaveCount(20); // Collapse the tree await page.getByTitle('Collapse all tree items').click(); @@ -293,44 +291,38 @@ test.describe('Recent Objects', () => { await page.getByRole('button', { name: 'Clear Recently Viewed' }).click(); // Click on the "OK" button in the confirmation dialog - await page.getByRole('button', { name: 'OK', exact: true }).click(); + await page.getByRole('button', { name: 'Ok', exact: true }).click(); // Assert that the list is empty - expect(await recentObjectsList.locator('.c-recentobjects-listitem').count()).toBe(0); + await expect(recentObjectsList.locator('.c-recentobjects-listitem')).toHaveCount(0); }); test('Verify functionality of "clear" and "collapse pane" buttons', async ({ page }) => { // Assert that the list initially contains 3 objects (clock, folder, my items) - expect(await recentObjectsList.locator('.c-recentobjects-listitem').count()).toBe(3); + await expect(recentObjectsList.locator('.c-recentobjects-listitem')).toHaveCount(3); // Assert that the button is enabled - expect(await page.getByRole('button', { name: 'Clear Recently Viewed' }).isEnabled()).toBe( - true - ); + await expect(page.getByRole('button', { name: 'Clear Recently Viewed' })).toBeEnabled(); // Click the aria-label="Clear Recently Viewed" button await page.getByRole('button', { name: 'Clear Recently Viewed' }).click(); // Click on the "OK" button in the confirmation dialog - await page.getByRole('button', { name: 'OK', exact: true }).click(); + await page.getByRole('button', { name: 'Ok', exact: true }).click(); // Assert that the list is empty - expect(await recentObjectsList.locator('.c-recentobjects-listitem').count()).toBe(0); + await expect(recentObjectsList.locator('.c-recentobjects-listitem')).toHaveCount(0); // Assert that the button is disabled - expect(await page.getByRole('button', { name: 'Clear Recently Viewed' }).isEnabled()).toBe( - false - ); + await expect(page.getByRole('button', { name: 'Clear Recently Viewed' })).toBeDisabled(); // Navigate to folder object await page.goto(folderA.url); // Assert that the list contains 1 object - expect(await recentObjectsList.locator('.c-recentobjects-listitem').count()).toBe(1); + await expect(recentObjectsList.locator('.c-recentobjects-listitem')).toHaveCount(1); // Assert that the button is enabled - expect(await page.getByRole('button', { name: 'Clear Recently Viewed' }).isEnabled()).toBe( - true - ); + await expect(page.getByRole('button', { name: 'Clear Recently Viewed' })).toBeEnabled(); // Assert initial state of pane and collapse the Recent Objects panel await expect(page.getByLabel('Expand Recently Viewed Pane')).toBeHidden(); diff --git a/e2e/tests/functional/renaming.e2e.spec.js b/e2e/tests/functional/renaming.e2e.spec.js index f201c07028..80641fb29b 100644 --- a/e2e/tests/functional/renaming.e2e.spec.js +++ b/e2e/tests/functional/renaming.e2e.spec.js @@ -24,13 +24,13 @@ This test suite is dedicated to tests for renaming objects, and their global application effects. */ -import { createDomainObjectWithDefaults, renameObjectFromContextMenu } from '../../appActions.js'; +import { createDomainObjectWithDefaults } from '../../appActions.js'; import { expect, test } from '../../baseFixtures.js'; test.describe('Renaming objects', () => { test.beforeEach(async ({ page }) => { // Go to baseURL - await page.goto('./', { waitUntil: 'networkidle' }); + await page.goto('./', { waitUntil: 'domcontentloaded' }); }); test('When renaming objects, the browse bar and various components all update', async ({ @@ -73,3 +73,33 @@ test.describe('Renaming objects', () => { expect(title).toBe(clock.name); }); }); + +/** + * @param {import('@playwright/test').Page} page + * @param {string} myItemsFolderName + * @param {string} url + * @param {string} newName + */ +async function renameObjectFromContextMenu(page, url, newName) { + await openObjectTreeContextMenu(page, url); + await page.locator('li:text("Edit Properties")').click(); + const nameInput = page.getByLabel('Title', { exact: true }); + await nameInput.fill(''); + await nameInput.fill(newName); + await page.locator('[aria-label="Save"]').click(); +} + +/** + * Open the given `domainObject`'s context menu from the object tree. + * Expands the path to the object and scrolls to it if necessary. + * + * @param {import('@playwright/test').Page} page + * @param {string} url the url to the object + */ +async function openObjectTreeContextMenu(page, url) { + await page.goto(url); + await page.getByLabel('Show selected item in tree').click(); + await page.locator('.is-navigated-object').click({ + button: 'right' + }); +} diff --git a/e2e/tests/functional/search.e2e.spec.js b/e2e/tests/functional/search.e2e.spec.js index ccc0163b18..4b7073aef9 100644 --- a/e2e/tests/functional/search.e2e.spec.js +++ b/e2e/tests/functional/search.e2e.spec.js @@ -162,7 +162,7 @@ test.describe('Grand Search', () => { const searchResults = page.getByRole('listitem', { name: 'Object Search Result' }); // Verify that no results are found - expect(await searchResults.count()).toBe(0); + await expect(searchResults).toHaveCount(0); // Verify proper message appears await expect(page.getByText('No results found')).toBeVisible(); @@ -187,7 +187,7 @@ test.describe('Grand Search', () => { // Verify that one result is found await expect(searchResults).toBeVisible(); - expect(await searchResults.count()).toBe(1); + await expect(searchResults).toHaveCount(1); await expect(searchResults).toContainText(folderName); }); @@ -216,7 +216,7 @@ test.describe('Grand Search', () => { // Network requests for the composite telemetry with multiple items should be: // 1. batched request for latest telemetry using the bulk API - expect(networkRequests.length).toBe(1); + await expect.poll(() => networkRequests, { timeout: 10000 }).toHaveLength(1); await expect(page.getByRole('list', { name: 'Object Results' })).toContainText('Clock A'); }); @@ -282,7 +282,7 @@ test.describe('Grand Search', () => { // Get the search results const objectSearchResults = page.getByLabel('Object Search Result'); // Verify that two results are found - expect(await objectSearchResults.count()).toBe(2); + await expect(objectSearchResults).toHaveCount(2); }); }); diff --git a/e2e/tests/functional/staleness.e2e.spec.js b/e2e/tests/functional/staleness.e2e.spec.js index 0b2a00a536..d247674cae 100644 --- a/e2e/tests/functional/staleness.e2e.spec.js +++ b/e2e/tests/functional/staleness.e2e.spec.js @@ -29,8 +29,6 @@ test.describe('Staleness', () => { }); test('Does not show staleness after navigating from a stale object', async ({ page }) => { - const objectViewSelector = '.c-object-view'; - const isStaleClass = 'is-stale'; const staleSWG = await createDomainObjectWithDefaults(page, { type: 'Sine Wave Generator', name: 'SWG' @@ -51,7 +49,7 @@ test.describe('Staleness', () => { await navigateToObjectWithRealTime(page, staleSWG.url); // Assert that staleness is shown - await expect(page.locator(`${objectViewSelector} .${isStaleClass}`)).toBeAttached({ + await expect(page.getByLabel('Object View')).toHaveClass(/is-stale/, { timeout: 30 * 1000 // Give 30 seconds for the staleness to be updated }); @@ -59,6 +57,6 @@ test.describe('Staleness', () => { await page.goto(folder.url); // Verify that staleness is not shown - await expect(page.locator(`${objectViewSelector} .${isStaleClass}`)).not.toBeAttached(); + await expect(page.getByLabel('Object View')).not.toHaveClass(/is-stale/); }); }); diff --git a/e2e/tests/functional/tooltips.e2e.spec.js b/e2e/tests/functional/tooltips.e2e.spec.js index 8782871a56..42d831196c 100644 --- a/e2e/tests/functional/tooltips.e2e.spec.js +++ b/e2e/tests/functional/tooltips.e2e.spec.js @@ -21,19 +21,11 @@ *****************************************************************************/ /* -This test suite is dedicated to tests which can quickly verify that any openmct installation is -operable and that any type of testing can proceed. - -Ideally, smoke tests should make zero assumptions about how and where they are run. This makes them -more resilient to change and therefor a better indicator of failure. Smoke tests will also run quickly -as they cover a very "thin surface" of functionality. - -When deciding between authoring new smoke tests or functional tests, ask yourself "would I feel -comfortable running this test during a live mission?" Avoid creating or deleting Domain Objects. -Make no assumptions about the order that elements appear in the DOM. +This suite is dedicated to tests which verify that tooltips are displayed correctly. */ import { createDomainObjectWithDefaults, expandEntireTree } from '../../appActions.js'; +import { MISSION_TIME } from '../../constants.js'; import { expect, test } from '../../pluginFixtures.js'; test.describe('Verify tooltips', () => { @@ -48,7 +40,7 @@ test.describe('Verify tooltips', () => { const swg2Path = 'My Items / Folder Foo / Folder Bar / SWG 2'; const swg3Path = 'My Items / Folder Foo / Folder Bar / Folder Baz / SWG 3'; - test.beforeEach(async ({ page, openmctConfig }) => { + test.beforeEach(async ({ page }) => { await page.goto('./', { waitUntil: 'domcontentloaded' }); folder1 = await createDomainObjectWithDefaults(page, { @@ -89,7 +81,7 @@ test.describe('Verify tooltips', () => { await expandEntireTree(page); }); - test('display correct paths for LAD tables', async ({ page, openmctConfig }) => { + test('display correct paths for LAD tables', async ({ page }) => { // Create LAD table await createDomainObjectWithDefaults(page, { type: 'LAD Table', @@ -98,25 +90,32 @@ test.describe('Verify tooltips', () => { // Edit LAD table await page.getByLabel('Edit Object').click(); - // Add the Sine Wave Generator to the LAD table and save changes - await page.dragAndDrop(`text=${sineWaveObject1.name}`, '.c-lad-table-wrapper'); - await page.dragAndDrop(`text=${sineWaveObject2.name}`, '.c-lad-table-wrapper'); - await page.dragAndDrop(`text=${sineWaveObject3.name}`, '.c-lad-table-wrapper'); - await page.locator('button[title="Save"]').click(); + // Add the Sine Wave Generator to the LAD table and save changes. + //TODO Follow up with https://github.com/nasa/openmct/issues/7773 + await page.getByLabel(`Preview ${sineWaveObject1.name}`).dragTo(page.getByLabel('Object View')); + await page.getByLabel(`Preview ${sineWaveObject2.name}`).dragTo(page.getByLabel('Object View')); + await page.getByLabel(`Preview ${sineWaveObject3.name}`).dragTo(page.getByLabel('Object View')); + await page.getByRole('button', { name: 'Save' }).click(); await page.getByRole('listitem', { name: 'Save and Finish Editing' }).click(); await page.keyboard.down('Control'); + //Hover on something else + await page.getByRole('button', { name: 'Create' }).hover(); + //Hover over the first + await page.getByLabel('lad name').getByText(sineWaveObject1.name).hover(); + await expect(page.getByRole('tooltip', { name: sineWaveObject1.path })).toBeVisible(); - async function getToolTip(object) { - await page.locator('.c-create-button').hover(); - await page.getByLabel('lad name').getByText(object.name).hover(); - let tooltipText = await page.locator('.c-tooltip').textContent(); - return tooltipText.replace('\n', '').trim(); - } + //Hover on something else + await page.getByRole('button', { name: 'Create' }).hover(); + //Hover over second object + await page.getByLabel('lad name').getByText(sineWaveObject2.name).hover(); + await expect(page.getByRole('tooltip', { name: sineWaveObject2.path })).toBeVisible(); - expect(await getToolTip(sineWaveObject1)).toBe(sineWaveObject1.path); - expect(await getToolTip(sineWaveObject2)).toBe(sineWaveObject2.path); - expect(await getToolTip(sineWaveObject3)).toBe(sineWaveObject3.path); + //Hover on something else + await page.getByRole('button', { name: 'Create' }).hover(); + //Hover over third object + await page.getByLabel('lad name').getByText(sineWaveObject3.name).hover(); + await expect(page.getByRole('tooltip', { name: sineWaveObject3.path })).toBeVisible(); }); test('display correct paths for expanded and collapsed plot legend items', async ({ page }) => { @@ -128,66 +127,74 @@ test.describe('Verify tooltips', () => { // Edit Overlay Plot await page.getByLabel('Edit Object').click(); - // Add the Sine Wave Generator to the LAD table and save changes - await page.dragAndDrop(`text=${sineWaveObject1.name}`, '.gl-plot'); - await page.dragAndDrop(`text=${sineWaveObject2.name}`, '.gl-plot'); - await page.dragAndDrop(`text=${sineWaveObject3.name}`, '.gl-plot'); - await page.locator('button[title="Save"]').click(); + // Add the Sine Wave Generators to the and save changes + await page + .getByLabel('Preview SWG 1 generator Object') + .dragTo(page.getByLabel('Plot Container Style Target')); + await page + .getByLabel('Preview SWG 2 generator Object') + .dragTo(page.getByLabel('Plot Container Style Target')); + await page + .getByLabel('Preview SWG 3 generator Object') + .dragTo(page.getByLabel('Plot Container Style Target')); + + await page.getByRole('button', { name: 'Save' }).click(); await page.getByRole('listitem', { name: 'Save and Finish Editing' }).click(); + //Hover over Collapsed Plot Legend Components with the Control Key pressed await page.keyboard.down('Control'); - - async function getCollapsedLegendToolTip(object) { - await page.locator('.c-create-button').hover(); - await page - .locator('.plot-series-name', { has: page.locator(`text="${object.name} Hz"`) }) - .hover(); - let tooltipText = await page.locator('.c-tooltip').textContent(); - return tooltipText.replace('\n', '').trim(); - } - - async function getExpandedLegendToolTip(object) { - await page.locator('.c-create-button').hover(); - await page - .locator('.plot-series-name', { has: page.locator(`text="${object.name}"`) }) - .hover(); - let tooltipText = await page.locator('.c-tooltip').textContent(); - return tooltipText.replace('\n', '').trim(); - } - - expect(await getCollapsedLegendToolTip(sineWaveObject1)).toBe(sineWaveObject1.path); - expect(await getCollapsedLegendToolTip(sineWaveObject2)).toBe(sineWaveObject2.path); - expect(await getCollapsedLegendToolTip(sineWaveObject3)).toBe(sineWaveObject3.path); - + //Hover over first object + await page.getByText('SWG 1 Hz').hover(); + await expect(page.getByRole('tooltip')).toHaveText(sineWaveObject1.path); + //Hover over another object to clear + await page.getByRole('button', { name: 'create' }).hover(); + //Hover over second object + await page.getByText('SWG 2 Hz').hover(); + await expect(page.getByRole('tooltip')).toHaveText(sineWaveObject2.path); + //Hover over another object to clear + await page.getByRole('button', { name: 'create' }).hover(); + //Hover over third object + await page.getByText('SWG 3 Hz').hover(); + await expect(page.getByRole('tooltip')).toHaveText(sineWaveObject3.path); + //Release the Control Key await page.keyboard.up('Control'); + + //Expand the legend await page.locator('.gl-plot-legend__view-control.c-disclosure-triangle').click(); + + //Hover over Expanded Plot Legend Components with the Control Key pressed await page.keyboard.down('Control'); - expect(await getExpandedLegendToolTip(sineWaveObject1)).toBe(sineWaveObject1.path); - expect(await getExpandedLegendToolTip(sineWaveObject2)).toBe(sineWaveObject2.path); - expect(await getExpandedLegendToolTip(sineWaveObject3)).toBe(sineWaveObject3.path); + await page.getByLabel('Plot Legend Expanded').getByText('SWG 1').hover(); + await expect(page.getByRole('tooltip')).toHaveText(sineWaveObject1.path); + //Hover over another object to clear + await page.getByRole('button', { name: 'create' }).hover(); + //Hover over second object + await page.getByLabel('Plot Legend Expanded').getByText('SWG 2').hover(); + await expect(page.getByRole('tooltip')).toHaveText(sineWaveObject2.path); + //Hover over another object to clear + await page.getByRole('button', { name: 'create' }).hover(); + //Hover over third object + await page.getByLabel('Plot Legend Expanded').getByText('SWG 3').hover(); + await expect(page.getByRole('tooltip')).toHaveText(sineWaveObject3.path); }); test('display correct paths when hovering over object labels', async ({ page }) => { - async function getObjectLabelTooltip(object) { - await page - .locator('.c-tree__item__name.c-object-label__name', { - has: page.locator(`text="${object.name}"`) - }) - .click(); - await page.keyboard.down('Control'); - await page - .locator('.l-browse-bar__object-name.c-object-label__name', { - has: page.locator(`text="${object.name}"`) - }) - .hover(); - const tooltipText = await page.locator('.c-tooltip').textContent(); - await page.keyboard.up('Control'); - return tooltipText.replace('\n', '').trim(); - } + //Navigate to SWG 1 in Tree + await page.getByLabel('Navigate to SWG 1 generator').click(); - expect(await getObjectLabelTooltip(sineWaveObject1)).toBe(sineWaveObject1.path); - expect(await getObjectLabelTooltip(sineWaveObject3)).toBe(sineWaveObject3.path); + //Expect tooltip to be the path of SWG 1 + await page.keyboard.down('Control'); + await page.getByRole('main').getByText('SWG 1', { exact: true }).hover(); + await expect(page.getByRole('tooltip')).toHaveText(sineWaveObject1.path); + await page.keyboard.up('Control'); + + //Navigate to SWG 3 in Tree + await page.getByLabel('Navigate to SWG 3 generator').click(); + //Expect tooltip to be the path of SWG 3 + await page.keyboard.down('Control'); + await page.getByRole('main').getByText('SWG 3', { exact: true }).hover(); + await expect(page.getByRole('tooltip')).toHaveText(sineWaveObject3.path); }); test('display correct paths when hovering over display layout pane headers', async ({ page }) => { @@ -198,8 +205,11 @@ test.describe('Verify tooltips', () => { }); // Edit Overlay Plot await page.getByLabel('Edit Object').click(); - await page.dragAndDrop(`text=${sineWaveObject1.name}`, '.gl-plot'); - await page.locator('button[title="Save"]').click(); + + await page + .getByLabel('Preview SWG 1 generator Object') + .dragTo(page.getByLabel('Plot Container Style Target')); + await page.getByRole('button', { name: 'Save' }).click(); await page.getByRole('listitem', { name: 'Save and Finish Editing' }).click(); // Create Stacked Plot @@ -209,8 +219,9 @@ test.describe('Verify tooltips', () => { }); // Edit Stacked Plot await page.getByLabel('Edit Object').click(); - await page.dragAndDrop(`text=${sineWaveObject2.name}`, '.c-plot--stacked.holder'); - await page.locator('button[title="Save"]').click(); + + await page.getByLabel(`Preview ${sineWaveObject2.name}`).dragTo(page.getByLabel('Object View')); + await page.getByRole('button', { name: 'Save' }).click(); await page.getByRole('listitem', { name: 'Save and Finish Editing' }).click(); // Create Display Layout @@ -221,66 +232,77 @@ test.describe('Verify tooltips', () => { // Edit Display Layout await page.getByLabel('Edit Object').click(); - await page.dragAndDrop("text='Test Overlay Plot'", '.l-layout__grid-holder', { - targetPosition: { x: 0, y: 0 } - }); - await page.dragAndDrop("text='Test Stacked Plot'", '.l-layout__grid-holder', { - targetPosition: { x: 0, y: 250 } - }); - await page.dragAndDrop(`text=${sineWaveObject3.name}`, '.l-layout__grid-holder', { - targetPosition: { x: 500, y: 200 } - }); - await page.locator('button[title="Save"]').click(); + await page + .getByLabel('Preview Test Overlay Plot') + .dragTo(page.locator('#display-layout-drop-area'), { + targetPosition: { x: 0, y: 0 } + }); + + //Add Display Layout below Overlay Plot + await page + .getByLabel('Preview Test Stacked Plot') + .dragTo(page.locator('#display-layout-drop-area'), { + targetPosition: { x: 0, y: 250 } + }); + + //Drag the SWG3 Object to the Display off to the right + await page + .getByLabel('Preview SWG 3 generator Object') + .dragTo(page.locator('#display-layout-drop-area'), { + targetPosition: { x: 500, y: 200 } + }); + + await page.getByRole('button', { name: 'Save' }).click(); await page.getByRole('listitem', { name: 'Save and Finish Editing' }).click(); + //Hover over Overlay Plot with the Control Key pressed await page.keyboard.down('Control'); - await page.getByText('Test Overlay Plot').nth(2).hover(); - let tooltipText = await page.locator('.c-tooltip').textContent(); - tooltipText = tooltipText.replace('\n', '').trim(); - expect(tooltipText).toBe('My Items / Test Overlay Plot'); - + //Hover Overlay Plot + await page.getByTitle('Test Overlay Plot').hover(); + await expect(page.getByRole('tooltip')).toHaveText('My Items / Test Overlay Plot'); await page.keyboard.up('Control'); - await page.locator('.c-plot-legend__view-control >> nth=0').click(); + + //Expand the Overlay Plot Legend and hover over the first legend item + await page.getByLabel('Expand Test Overlay Plot Legend').click(); + await page.keyboard.down('Control'); - await page.locator('.plot-wrapper-expanded-legend .plot-series-name').first().hover(); - tooltipText = await page.locator('.c-tooltip').textContent(); - tooltipText = tooltipText.replace('\n', '').trim(); - expect(tooltipText).toBe(sineWaveObject1.path); + await page.getByLabel('Plot Legend Item for Test').getByText('SWG').hover(); + await expect(page.getByRole('tooltip')).toHaveText(sineWaveObject1.path); - await page.getByText('Test Stacked Plot').nth(2).hover(); - tooltipText = await page.locator('.c-tooltip').textContent(); - tooltipText = tooltipText.replace('\n', '').trim(); - expect(tooltipText).toBe('My Items / Test Stacked Plot'); + //Hover over Stacked Plot Title + await page.getByTitle('Test Stacked Plot').hover(); + await expect(page.getByRole('tooltip')).toHaveText('My Items / Test Stacked Plot'); - await page.getByText('SWG 3').nth(2).hover(); - tooltipText = await page.locator('.c-tooltip').textContent(); - tooltipText = tooltipText.replace('\n', '').trim(); - expect(sineWaveObject3.path).toBe(tooltipText); + //Hover over SWG3 Object + await page.getByLabel('Alpha-numeric telemetry name for SWG').hover(); + await expect(page.getByRole('tooltip')).toHaveText(sineWaveObject3.path); }); test('display correct paths when hovering over flexible object labels', async ({ page }) => { + //Create Flexible Layout await createDomainObjectWithDefaults(page, { type: 'Flexible Layout', name: 'Test Flexible Layout' }); - await page.dragAndDrop(`text=${sineWaveObject1.name}`, '.c-fl-container >> nth=0'); - await page.dragAndDrop(`text=${sineWaveObject3.name}`, '.c-fl-container >> nth=1'); + //Add SWG1 and SWG3 to Flexible Layout + await page.getByLabel('Navigate to SWG 1 generator').dragTo(page.getByRole('row').nth(0)); + await page + .getByLabel('Preview SWG 3 generator Object') + .dragTo(page.getByLabel('Container Handle 2')); - await page.locator('button[title="Save"]').click(); + await page.getByRole('button', { name: 'Save' }).click(); await page.getByRole('listitem', { name: 'Save and Finish Editing' }).click(); + //Hover over SWG1 Object await page.keyboard.down('Control'); - await page.getByText('SWG 1').nth(2).hover(); - let tooltipText = await page.locator('.c-tooltip').textContent(); - tooltipText = tooltipText.replace('\n', '').trim(); - expect(tooltipText).toBe(sineWaveObject1.path); + await page.getByTitle('SWG 1').hover(); + await expect(page.getByRole('tooltip')).toHaveText(sineWaveObject1.path); - await page.getByText('SWG 3').nth(2).hover(); - tooltipText = await page.locator('.c-tooltip').textContent(); - tooltipText = tooltipText.replace('\n', '').trim(); - expect(tooltipText).toBe(sineWaveObject3.path); + //Hover over SWG3 Object + await page.getByTitle('SWG 3').hover(); + await expect(page.getByRole('tooltip')).toHaveText(sineWaveObject3.path); }); test('display correct paths when hovering over tab view labels', async ({ page }) => { @@ -289,46 +311,40 @@ test.describe('Verify tooltips', () => { name: 'Test Tabs View' }); - await page.dragAndDrop(`text=${sineWaveObject1.name}`, '.c-tabs-view__tabs-holder'); - await page.dragAndDrop(`text=${sineWaveObject3.name}`, '.c-tabs-view__tabs-holder'); + //Add SWG1 and SWG3 to Flexible Layout + await page + .getByLabel('Navigate to SWG 1 generator') + .dragTo(page.getByText('Drag objects here to add them')); + await page.getByLabel('Preview SWG 3 generator Object').dragTo(page.getByLabel('SWG 1 tab')); - await page.locator('button[title="Save"]').click(); + await page.getByRole('button', { name: 'Save' }).click(); await page.getByRole('listitem', { name: 'Save and Finish Editing' }).click(); await page.keyboard.down('Control'); - await page.getByText('SWG 1').nth(2).hover(); - let tooltipText = await page.locator('.c-tooltip').textContent(); - tooltipText = tooltipText.replace('\n', '').trim(); - expect(tooltipText).toBe(sineWaveObject1.path); + await page.getByLabel('SWG 1 tab').getByText('SWG').hover(); - await page.getByText('SWG 3').nth(2).hover(); - tooltipText = await page.locator('.c-tooltip').textContent(); - tooltipText = tooltipText.replace('\n', '').trim(); - expect(tooltipText).toBe(sineWaveObject3.path); + await expect(page.getByRole('tooltip')).toHaveText(sineWaveObject1.path); + + await page.getByLabel('SWG 3 tab').getByText('SWG').hover(); + await expect(page.getByRole('tooltip')).toHaveText(sineWaveObject3.path); }); test('display correct paths when hovering tree items', async ({ page }) => { await page.keyboard.down('Control'); - await page.getByText('SWG 1').nth(0).hover(); - let tooltipText = await page.locator('.c-tooltip').textContent(); - tooltipText = tooltipText.replace('\n', '').trim(); - expect(tooltipText).toBe(sineWaveObject1.path); + await page.getByText('SWG 1').first().hover(); + await expect(page.getByRole('tooltip')).toHaveText(sineWaveObject1.path); - await page.getByText('SWG 3').nth(0).hover(); - tooltipText = await page.locator('.c-tooltip').textContent(); - tooltipText = tooltipText.replace('\n', '').trim(); - expect(tooltipText).toBe(sineWaveObject3.path); + await page.getByText('SWG 3').first().hover(); + await expect(page.getByRole('tooltip')).toHaveText(sineWaveObject3.path); }); test('display correct paths when hovering search items', async ({ page }) => { await page.getByRole('searchbox', { name: 'Search Input' }).click(); - await page.fill('.c-search__input', 'SWG 3'); + await page.getByRole('searchbox', { name: 'Search Input' }).fill('SWG 3'); await page.keyboard.down('Control'); - await page.locator('.c-gsearch-result__title').hover(); - let tooltipText = await page.locator('.c-tooltip').textContent(); - tooltipText = tooltipText.replace('\n', '').trim(); - expect(tooltipText).toBe(sineWaveObject3.path); + await page.getByLabel('Object Results').getByText('SWG').hover(); + await expect(page.getByRole('tooltip')).toHaveText(sineWaveObject3.path); }); test('display path for source telemetry when hovering over gauge', async ({ page }) => { @@ -336,13 +352,14 @@ test.describe('Verify tooltips', () => { type: 'Gauge', name: 'Test Gauge' }); - await page.dragAndDrop(`text=${sineWaveObject3.name}`, '.c-gauge__wrapper'); + + await page.getByLabel('Navigate to SWG 3 generator').dragTo(page.getByRole('meter')); await page.keyboard.down('Control'); + // FIXME: We shouldn't need a `force: true` here, but the parent + // element blocks // eslint-disable-next-line playwright/no-force-option - await page.locator('.c-gauge.c-dial').hover({ position: { x: 0, y: 0 }, force: true }); - let tooltipText = await page.locator('.c-tooltip').textContent(); - tooltipText = tooltipText.replace('\n', '').trim(); - expect(tooltipText).toBe(sineWaveObject3.path); + await page.getByRole('meter').hover({ position: { x: 0, y: 0 }, force: true }); + await expect(page.getByRole('tooltip')).toHaveText(sineWaveObject3.path); }); test('display tooltip path for notebook embeds', async ({ page }) => { @@ -351,78 +368,72 @@ test.describe('Verify tooltips', () => { name: 'Test Notebook' }); - await page.dragAndDrop(`text=${sineWaveObject3.name}`, '.c-notebook__drag-area'); + await page + .getByLabel('Navigate to SWG 3 generator') + .dragTo(page.getByLabel('To start a new entry, click')); await page.keyboard.down('Control'); - await page.locator('.c-ne__embed').hover(); - let tooltipText = await page.locator('.c-tooltip').textContent(); - tooltipText = tooltipText.replace('\n', '').trim(); - expect(tooltipText).toBe(sineWaveObject3.path); + await page.getByLabel('SWG 3 Notebook Embed').hover(); + await expect(page.getByRole('tooltip')).toHaveText(sineWaveObject3.path); }); - test.fixme('display tooltip path for telemetry table names', async ({ page }) => { - test.info().annotations.push({ - type: 'issue', - description: 'https://github.com/nasa/openmct/issues/7421' - }); - // set endBound to 10 seconds after start bound - const url = await page.url(); - const parsedUrl = new URL(url.replace('#', '!')); - const startBound = Number(parsedUrl.searchParams.get('tc.startBound')); - const tenSecondsInMilliseconds = 10 * 1000; - const endBound = startBound + tenSecondsInMilliseconds; - parsedUrl.searchParams.set('tc.endBound', endBound); - await page.goto(parsedUrl.href.replace('!', '#')); - + test('display tooltip path for telemetry table names @clock', async ({ page }) => { + await page.clock.install({ time: MISSION_TIME }); + await page.clock.resume(); await createDomainObjectWithDefaults(page, { type: 'Telemetry Table', name: 'Test Telemetry Table' }); - await page.dragAndDrop(`text=${sineWaveObject1.name}`, '.c-telemetry-table'); - await page.dragAndDrop(`text=${sineWaveObject3.name}`, '.c-telemetry-table'); + await page + .getByLabel(`Navigate to ${sineWaveObject1.name}`) + .dragTo(page.getByLabel('Object View')); + await page.getByLabel(`Preview ${sineWaveObject3.name}`).dragTo(page.getByLabel('Object View')); - await page.locator('button[title="Save"]').click(); + await page.getByRole('button', { name: 'Save' }).click(); await page.getByRole('listitem', { name: 'Save and Finish Editing' }).click(); + + // Confirm that telemetry rows exist for SWG 1 and 3 and are in view + await expect(page.getByLabel('name table cell SWG 1').first()).toBeInViewport(); + await expect(page.getByLabel('name table cell SWG 3').first()).toBeInViewport(); + + // Pause to prevent more telemetry from streaming in + await page.clock.pauseAt(MISSION_TIME + 30 * 1000); + // Run for 30 seconds to allow SOME telemetry to stream in + await page.clock.runFor(30 * 1000); + await page.keyboard.down('Control'); + // Hover over SWG3 in Telemetry Table + await page.getByLabel('name table cell SWG 3').first().hover(); + await expect(page.getByRole('tooltip', { name: sineWaveObject3.path })).toBeVisible(); - await page.locator('.noselect > [title="SWG 3"]').first().hover(); - let tooltipText = await page.locator('.c-tooltip').textContent(); - tooltipText = tooltipText.replace('\n', '').trim(); - expect(tooltipText).toBe(sineWaveObject3.path); + // Release Control Key + await page.keyboard.up('Control'); + // Hover somewhere else so the tooltip goes away + await page.getByLabel('Navigate to Test Telemetry Table').hover(); + await expect(page.getByRole('tooltip')).toBeHidden(); - await page.locator('.noselect > [title="SWG 1"]').first().hover(); - tooltipText = await page.locator('.c-tooltip').textContent(); - tooltipText = tooltipText.replace('\n', '').trim(); - expect(tooltipText).toBe(sineWaveObject1.path); + await page.keyboard.down('Control'); + // Hover over SWG1 in Telemetry Table + await page.getByLabel('name table cell SWG 1').first().hover(); + await expect(page.getByRole('tooltip', { name: sineWaveObject1.path })).toBeVisible(); }); test('display tooltip path for recently viewed items', async ({ page }) => { // drag up Recently Viewed pane - await page - .locator('.l-pane.l-pane--vertical-handle-before', { - hasText: 'Recently Viewed' - }) - .locator('.l-pane__handle') - .hover(); + await page.getByLabel('Resize Recently Viewed Pane').hover(); await page.mouse.down(); await page.mouse.move(0, 300); await page.mouse.up(); await page.keyboard.down('Control'); await page.getByLabel('Recent Objects').getByText(sineWaveObject3.name).hover(); - let tooltipText = await page.locator('.c-tooltip').textContent(); - tooltipText = tooltipText.replace('\n', '').trim(); - expect(tooltipText).toBe(sineWaveObject3.path); + await expect(page.getByRole('tooltip')).toHaveText(sineWaveObject3.path); await page.getByLabel('Recent Objects').getByText(sineWaveObject2.name).hover(); - tooltipText = await page.locator('.c-tooltip').textContent(); - tooltipText = tooltipText.replace('\n', '').trim(); - expect(tooltipText).toBe(sineWaveObject2.path); + await expect(page.getByRole('tooltip')).toHaveText(sineWaveObject2.path); await page.getByLabel('Recent Objects').getByText(sineWaveObject1.name).hover(); - tooltipText = await page.locator('.c-tooltip').textContent(); - tooltipText = tooltipText.replace('\n', '').trim(); - expect(tooltipText).toBe(sineWaveObject1.path); + await expect(page.getByRole('tooltip')).toHaveText(sineWaveObject1.path); }); test('display tooltip path for time strips', async ({ page }) => { @@ -433,35 +444,27 @@ test.describe('Verify tooltips', () => { }); // Edit Overlay Plot await page.getByLabel('Edit Object').click(); - await page.dragAndDrop( - `text=${sineWaveObject1.name}`, - '.c-object-view.is-object-type-time-strip' - ); - await page.dragAndDrop( - `text=${sineWaveObject2.name}`, - '.c-object-view.is-object-type-time-strip' - ); - await page.dragAndDrop( - `text=${sineWaveObject3.name}`, - '.c-object-view.is-object-type-time-strip' - ); - await page.locator('button[title="Save"]').click(); + await page + .getByLabel(`Preview ${sineWaveObject1.name}`) + .dragTo(page.getByLabel('Test Time Strip Object View')); + await page + .getByLabel(`Preview ${sineWaveObject2.name}`) + .dragTo(page.getByLabel('Test Time Strip Object View')); + await page + .getByLabel(`Preview ${sineWaveObject3.name}`) + .dragTo(page.getByLabel('Test Time Strip Object View')); + + await page.getByRole('button', { name: 'Save' }).click(); await page.getByRole('listitem', { name: 'Save and Finish Editing' }).click(); await page.keyboard.down('Control'); await page.getByText(sineWaveObject1.name).nth(2).hover(); - let tooltipText = await page.locator('.c-tooltip').textContent(); - tooltipText = tooltipText.replace('\n', '').trim(); - expect(tooltipText).toBe(sineWaveObject1.path); + await expect(page.getByRole('tooltip')).toHaveText(sineWaveObject1.path); await page.getByText(sineWaveObject2.name).nth(2).hover(); - tooltipText = await page.locator('.c-tooltip').textContent(); - tooltipText = tooltipText.replace('\n', '').trim(); - expect(tooltipText).toBe(sineWaveObject2.path); + await expect(page.getByRole('tooltip')).toHaveText(sineWaveObject2.path); await page.getByText(sineWaveObject3.name).nth(2).hover(); - tooltipText = await page.locator('.c-tooltip').textContent(); - tooltipText = tooltipText.replace('\n', '').trim(); - expect(tooltipText).toBe(sineWaveObject3.path); + await expect(page.getByRole('tooltip')).toHaveText(sineWaveObject3.path); }); }); diff --git a/e2e/tests/functional/tree.e2e.spec.js b/e2e/tests/functional/tree.e2e.spec.js index 0db239205f..5bd6ca9086 100644 --- a/e2e/tests/functional/tree.e2e.spec.js +++ b/e2e/tests/functional/tree.e2e.spec.js @@ -20,7 +20,7 @@ * at runtime from the About dialog for additional information. *****************************************************************************/ -import { createDomainObjectWithDefaults, renameObjectFromContextMenu } from '../../appActions.js'; +import { createDomainObjectWithDefaults } from '../../appActions.js'; import { expect, test } from '../../pluginFixtures.js'; test.describe('Main Tree', () => { @@ -47,123 +47,139 @@ test.describe('Main Tree', () => { parent: folder.uuid }); - await expandTreePaneItemByName(page, folder.name); - await assertTreeItemIsVisible(page, clock.name); + await page.getByLabel(`Expand ${folder.name} folder`).click(); + + await expect( + page.getByRole('tree', { name: 'Main Tree' }).getByRole('treeitem', { name: clock.name }) + ).toBeVisible(); }); test('Creating a child object on one tab and expanding its parent on the other shows the correct composition @2p', async ({ - page, - openmctConfig + page }) => { test.info().annotations.push({ type: 'issue', description: 'https://github.com/nasa/openmct/issues/6391' }); - const { myItemsFolderName } = openmctConfig; const page2 = await page.context().newPage(); // Both pages: Go to baseURL await Promise.all([ - page.goto('./', { waitUntil: 'networkidle' }), - page2.goto('./', { waitUntil: 'networkidle' }) + page.goto('./', { waitUntil: 'domcontentloaded' }), + page2.goto('./', { waitUntil: 'domcontentloaded' }) + ]); + + await Promise.all([ + page.waitForURL('**/browse/mine?**'), + page2.waitForURL('**/browse/mine?**') ]); const page1Folder = await createDomainObjectWithDefaults(page, { type: 'Folder' }); - await expandTreePaneItemByName(page2, myItemsFolderName); - await assertTreeItemIsVisible(page2, page1Folder.name); + await page2.getByLabel('Expand My Items folder').click(); + + await expect( + page2 + .getByRole('tree', { name: 'Main Tree' }) + .getByRole('treeitem', { name: page1Folder.name }) + ).toBeVisible(); }); test('Creating a child object on one tab and expanding its parent on the other shows the correct composition @couchdb @2p', async ({ - page, - openmctConfig + page }) => { test.info().annotations.push({ type: 'issue', description: 'https://github.com/nasa/openmct/issues/6391' }); - const { myItemsFolderName } = openmctConfig; const page2 = await page.context().newPage(); // Both pages: Go to baseURL await Promise.all([ - page.goto('./', { waitUntil: 'networkidle' }), - page2.goto('./', { waitUntil: 'networkidle' }) + page.goto('./', { waitUntil: 'domcontentloaded' }), + page2.goto('./', { waitUntil: 'domcontentloaded' }) + ]); + + await Promise.all([ + page.waitForURL('**/browse/mine?**'), + page2.waitForURL('**/browse/mine?**') ]); const page1Folder = await createDomainObjectWithDefaults(page, { type: 'Folder' }); - await expandTreePaneItemByName(page2, myItemsFolderName); - await assertTreeItemIsVisible(page2, page1Folder.name); + await page2.getByLabel('Expand My Items folder').click(); + await expect( + page2 + .getByRole('tree', { name: 'Main Tree' }) + .getByRole('treeitem', { name: page1Folder.name }) + ).toBeVisible(); }); - test('Renaming an object reorders the tree @unstable', async ({ page, openmctConfig }) => { - const { myItemsFolderName } = openmctConfig; - - await createDomainObjectWithDefaults(page, { + test('Renaming an object reorders the tree', async ({ page }) => { + const foo = await createDomainObjectWithDefaults(page, { type: 'Folder', name: 'Foo' }); - await createDomainObjectWithDefaults(page, { + const bar = await createDomainObjectWithDefaults(page, { type: 'Folder', name: 'Bar' }); - await createDomainObjectWithDefaults(page, { + const baz = await createDomainObjectWithDefaults(page, { type: 'Folder', name: 'Baz' }); - const clock1 = await createDomainObjectWithDefaults(page, { + let clock1 = await createDomainObjectWithDefaults(page, { type: 'Clock', name: 'aaa' }); - await createDomainObjectWithDefaults(page, { + const www = await createDomainObjectWithDefaults(page, { type: 'Clock', name: 'www' }); // Expand the root folder - await expandTreePaneItemByName(page, myItemsFolderName); + await page.getByLabel('Expand My Items folder').click(); await test.step('Reorders objects with the same tree depth', async () => { - await getAndAssertTreeItems(page, ['aaa', 'Bar', 'Baz', 'Foo', 'www']); - await renameObjectFromContextMenu(page, clock1.url, 'zzz'); - await getAndAssertTreeItems(page, ['Bar', 'Baz', 'Foo', 'www', 'zzz']); + await getAndAssertTreeItems(page, ['My Items', 'aaa', 'Bar', 'Baz', 'Foo', 'www']); + clock1.name = 'zzz'; + await renameObjectFromContextMenu(page, clock1.url, clock1.name); + await getAndAssertTreeItems(page, ['My Items', 'Bar', 'Baz', 'Foo', 'www', 'zzz']); }); await test.step('Reorders links to objects as well as original objects', async () => { - await page.click('role=treeitem[name=/Bar/]'); - await page.dragAndDrop('role=treeitem[name=/www/]', '.c-object-view'); - await page.dragAndDrop('role=treeitem[name=/zzz/]', '.c-object-view'); - await page.click('role=treeitem[name=/Baz/]'); - await page.dragAndDrop('role=treeitem[name=/www/]', '.c-object-view'); - await page.dragAndDrop('role=treeitem[name=/zzz/]', '.c-object-view'); - await page.click('role=treeitem[name=/Foo/]'); - await page.dragAndDrop('role=treeitem[name=/www/]', '.c-object-view'); - await page.dragAndDrop('role=treeitem[name=/zzz/]', '.c-object-view'); + await page.getByLabel(`Navigate to ${bar.name}`).dragTo(page.getByLabel('Object View')); + await page.getByLabel(`Navigate to ${www.name}`).dragTo(page.getByLabel('Object View')); + await page.getByLabel(`Navigate to ${clock1.name}`).dragTo(page.getByLabel('Object View')); + await page.getByLabel(`Navigate to ${baz.name}`).dragTo(page.getByLabel('Object View')); + await page.getByLabel(`Navigate to ${www.name}`).dragTo(page.getByLabel('Object View')); + await page.getByLabel(`Navigate to ${clock1.name}`).dragTo(page.getByLabel('Object View')); + await page.goto(foo.url); + await page.getByLabel(`Navigate to ${www.name}`).dragTo(page.getByLabel('Object View')); + await page.getByLabel(`Navigate to ${clock1.name}`).dragTo(page.getByLabel('Object View')); // Expand the unopened folders - await expandTreePaneItemByName(page, 'Bar'); - await expandTreePaneItemByName(page, 'Baz'); - await expandTreePaneItemByName(page, 'Foo'); + await page.getByLabel(`Expand Bar folder`).click(); + await page.getByLabel(`Expand Baz folder`).click(); + await page.getByLabel(`Expand Foo folder`).click(); - await renameObjectFromContextMenu(page, clock1.url, '___'); + clock1.name = '___'; + await renameObjectFromContextMenu(page, clock1.url, clock1.name); + await expect(page.getByLabel('Navigate to ' + clock1.name)).toHaveCount(2); await getAndAssertTreeItems(page, [ + 'My Items', '___', 'Bar', - '___', - 'www', 'Baz', - '___', - 'www', 'Foo', '___', 'www', @@ -172,10 +188,8 @@ test.describe('Main Tree', () => { }); }); test('Opening and closing an item before the request has been fulfilled will abort the request @couchdb', async ({ - page, - openmctConfig + page }) => { - const { myItemsFolderName } = openmctConfig; let requestWasAborted = false; page.on('requestfailed', (request) => { @@ -201,7 +215,7 @@ test.describe('Main Tree', () => { // Quickly Expand/close the root folder await page .getByRole('button', { - name: `Expand ${myItemsFolderName} folder` + name: `Expand My Items folder` }) .dblclick({ delay: 400 }); @@ -214,35 +228,36 @@ test.describe('Main Tree', () => { * @param {Array} expected */ async function getAndAssertTreeItems(page, expected) { - const treeItems = page.locator('[role="treeitem"]'); - const allTexts = await treeItems.allInnerTexts(); - // Get rid of root folder ('My Items') as its position will not change - allTexts.shift(); - expect(allTexts).toEqual(expected); -} - -async function assertTreeItemIsVisible(page, name) { - const mainTree = page.getByRole('tree', { - name: 'Main Tree' - }); - const treeItem = mainTree.getByRole('treeitem', { - name - }); - - await expect(treeItem).toBeVisible(); + const treeItems = page.getByRole('treeitem'); + await expect(treeItems).toHaveCount(expected.length); + await expect(treeItems).toHaveText(expected, { useInnerText: true }); } /** * @param {import('@playwright/test').Page} page - * @param {string} name + * @param {string} myItemsFolderName + * @param {string} url + * @param {string} newName */ -async function expandTreePaneItemByName(page, name) { - const mainTree = page.getByRole('tree', { - name: 'Main Tree' - }); - const treeItem = mainTree.getByRole('treeitem', { - name, - expanded: false - }); - await treeItem.locator('.c-disclosure-triangle').click(); +async function renameObjectFromContextMenu(page, url, newName) { + await openObjectTreeContextMenu(page, url); + await page.getByLabel('Edit Properties...').click(); + const nameInput = page.getByLabel('Title', { exact: true }); + await nameInput.fill(newName); + await page.getByLabel('Save').click(); +} + +/** + * Open the given `domainObject`'s context menu from the object tree. + * Expands the path to the object and scrolls to it if necessary. + * + * @param {import('@playwright/test').Page} page + * @param {string} url the url to the object + */ +async function openObjectTreeContextMenu(page, url) { + await page.goto(url); + await page.getByLabel('Show selected item in tree').click(); + await page.locator('.is-navigated-object').click({ + button: 'right' + }); } diff --git a/e2e/tests/mobile/smoke.e2e.spec.js b/e2e/tests/mobile/smoke.e2e.spec.js index cd0ad707a4..adb39820c0 100644 --- a/e2e/tests/mobile/smoke.e2e.spec.js +++ b/e2e/tests/mobile/smoke.e2e.spec.js @@ -62,7 +62,7 @@ test.describe('Smoke tests for @mobile', () => { test('Verify that user can change time conductor @mobile', async ({ page }) => { //Collapse Browse Pane to get more Time Conductor space await page.getByLabel('Collapse Browse Pane').click(); - //Open Time Conductor and change to Real Time Mode and set offset hour by 1 hour + // Open Time Conductor and change to Real Time Mode and set offset hour by 1 hour // Disabling line because we're intentionally obscuring the text // eslint-disable-next-line playwright/no-force-option await page.getByLabel('Time Conductor Mode').click({ force: true }); @@ -82,14 +82,14 @@ test.describe('Smoke tests for @mobile', () => { await page.getByTitle('Collapse Browse Pane').click(); await expect(page.getByRole('main').getByText('Parent Display Layout')).toBeVisible(); //Verify both objects are in view - await expect(await page.getByLabel('Child Layout 1 Layout')).toBeVisible(); - await expect(await page.getByLabel('Child Layout 2 Layout')).toBeVisible(); + await expect(page.getByLabel('Child Layout 1 Layout')).toBeVisible(); + await expect(page.getByLabel('Child Layout 2 Layout')).toBeVisible(); //Remove First Object to bring up confirmation dialog await page.getByLabel('View menu items').nth(1).click(); await page.getByLabel('Remove').click(); - await page.getByRole('button', { name: 'OK' }).click(); + await page.getByRole('button', { name: 'Ok' }).click(); //Verify that the object is removed - await expect(await page.getByLabel('Child Layout 1 Layout')).toBeVisible(); - expect(await page.getByLabel('Child Layout 2 Layout').count()).toBe(0); + await expect(page.getByLabel('Child Layout 1 Layout')).toBeVisible(); + await expect(page.getByLabel('Child Layout 2 Layout')).toHaveCount(0); }); }); diff --git a/e2e/tests/performance/contract/imagery.contract.perf.spec.js b/e2e/tests/performance/contract/imagery.contract.perf.spec.js index 7b3b00b6cf..c1f64dd890 100644 --- a/e2e/tests/performance/contract/imagery.contract.perf.spec.js +++ b/e2e/tests/performance/contract/imagery.contract.perf.spec.js @@ -39,7 +39,7 @@ const filePath = 'test-data/PerformanceDisplayLayout.json'; test.describe('Performance tests', () => { test.beforeEach(async ({ page, browser }, testInfo) => { // Go to baseURL - await page.goto('./', { waitUntil: 'networkidle' }); + await page.goto('./', { waitUntil: 'domcontentloaded' }); // Click a:has-text("My Items") await page.locator('a:has-text("My Items")').click({ @@ -129,12 +129,12 @@ test.describe('Performance tests', () => { ]); //Time to Example Imagery Frame loads within Display Layout - await page.waitForSelector('.c-imagery__main-image__bg', { state: 'visible' }); + await page.locator('.c-imagery__main-image__bg').waitFor({ state: 'visible' }); //Time to Example Imagery object loads - await page.waitForSelector('.c-imagery__main-image__background-image', { state: 'visible' }); + await page.locator('.c-imagery__main-image__background-image').waitFor({ state: 'visible' }); //Get background-image url from background-image css prop - const backgroundImage = await page.locator('.c-imagery__main-image__background-image'); + const backgroundImage = page.locator('.c-imagery__main-image__background-image'); let backgroundImageUrl = await backgroundImage.evaluate((el) => { return window .getComputedStyle(el) @@ -156,15 +156,15 @@ test.describe('Performance tests', () => { await page.evaluate(() => window.performance.mark('viewLarge.start.test')); //This is a mark only to compare evaluate timing //Time to Imagery Rendered in Large Frame - await page.waitForSelector('.c-imagery__main-image__bg', { state: 'visible' }); + await page.locator('.c-imagery__main-image__bg').waitFor({ state: 'visible' }); await page.evaluate(() => window.performance.mark('background-image-frame')); //Time to Example Imagery object loads - await page.waitForSelector('.c-imagery__main-image__background-image', { state: 'visible' }); + await page.locator('.c-imagery__main-image__background-image').waitFor({ state: 'visible' }); await page.evaluate(() => window.performance.mark('background-image-visible')); // Get Current number of images in thumbstrip - await page.waitForSelector('.c-imagery__thumb'); + await page.locator('.c-imagery__thumb').waitFor({ state: 'visible' }); const thumbCount = await page.locator('.c-imagery__thumb').count(); console.log('number of thumbs rendered ' + thumbCount); await page.locator('.c-imagery__thumb').last().click(); diff --git a/e2e/tests/performance/contract/notebook.contract.perf.spec.js b/e2e/tests/performance/contract/notebook.contract.perf.spec.js index 5a63d73ed3..27e7a1a703 100644 --- a/e2e/tests/performance/contract/notebook.contract.perf.spec.js +++ b/e2e/tests/performance/contract/notebook.contract.perf.spec.js @@ -38,7 +38,7 @@ const notebookFilePath = 'test-data/PerformanceNotebook.json'; test.describe('Performance tests', () => { test.beforeEach(async ({ page, browser }, testInfo) => { // Go to baseURL - await page.goto('./', { waitUntil: 'networkidle' }); + await page.goto('./', { waitUntil: 'domcontentloaded' }); // Click a:has-text("My Items") await page.locator('a:has-text("My Items")').click({ @@ -110,20 +110,19 @@ test.describe('Performance tests', () => { await page.evaluate(() => window.performance.mark('search-entered')); //Search Result Appears and is clicked await Promise.all([ - page.waitForNavigation(), page.locator('a:has-text("Performance Notebook")').first().click(), page.evaluate(() => window.performance.mark('click-search-result')) ]); - await page.waitForSelector('.c-tree__item c-tree-and-search__loading loading', { - state: 'hidden' - }); + await page + .locator('.c-tree__item c-tree-and-search__loading loading') + .waitFor({ state: 'hidden' }); await page.evaluate(() => window.performance.mark('search-spinner-gone')); - await page.waitForSelector('.l-browse-bar__object-name', { state: 'visible' }); + await page.locator('.l-browse-bar__object-name').waitFor({ state: 'visible' }); await page.evaluate(() => window.performance.mark('object-title-appears')); - await page.waitForSelector('.c-notebook__entry >> nth=0', { state: 'visible' }); + await page.locator('.c-notebook__entry >> nth=0').waitFor({ state: 'visible' }); await page.evaluate(() => window.performance.mark('notebook-entry-appears')); // Click Add new Notebook Entry @@ -139,9 +138,9 @@ test.describe('Performance tests', () => { await page.evaluate(() => window.performance.mark('notebook-search-start')); await page.locator('.c-notebook__search >> input').fill('Existing Entry'); await page.evaluate(() => window.performance.mark('notebook-search-filled')); - await page.waitForSelector('text=Search Results (3)', { state: 'visible' }); + await page.locator('text=Search Results (3)').waitFor({ state: 'visible' }); await page.evaluate(() => window.performance.mark('notebook-search-processed')); - await page.waitForSelector('.c-notebook__entry >> nth=2', { state: 'visible' }); + await page.locator('.c-notebook__entry >> nth=2').waitFor({ state: 'visible' }); await page.evaluate(() => window.performance.mark('notebook-search-processed')); //Clear Search @@ -154,7 +153,7 @@ test.describe('Performance tests', () => { await page.locator('div.c-ne__time-and-content').last().hover(); await page.locator('button[title="Delete this entry"]').last().click(); await page.locator('button:has-text("Ok")').click(); - await page.waitForSelector('.c-notebook__entry >> nth=3', { state: 'detached' }); + await page.locator('.c-notebook__entry >> nth=3').waitFor({ state: 'detached' }); await page.evaluate(() => window.performance.mark('new-notebook-entry-deleted')); //await client.send('HeapProfiler.enable'); diff --git a/e2e/tests/performance/memory/navigation.memory.perf.spec.js b/e2e/tests/performance/memory/navigation.memory.perf.spec.js index cb74b6a11a..022c0bb1c2 100644 --- a/e2e/tests/performance/memory/navigation.memory.perf.spec.js +++ b/e2e/tests/performance/memory/navigation.memory.perf.spec.js @@ -228,9 +228,7 @@ test.describe('Navigation memory leak is not detected in', () => { expect(result).toBe(true); }); - test('display layout with plots of swgs, alphanumerics, and condition sets, ', async ({ - page - }) => { + test('display layout with plots of swgs, alphanumerics, and condition sets', async ({ page }) => { const result = await navigateToObjectAndDetectMemoryLeak( page, 'display-layout-simple-telemetry' diff --git a/e2e/tests/performance/tabs.perf.spec.js b/e2e/tests/performance/tabs.perf.spec.js index 3db219f1da..6141bf61ee 100644 --- a/e2e/tests/performance/tabs.perf.spec.js +++ b/e2e/tests/performance/tabs.perf.spec.js @@ -78,7 +78,7 @@ test.describe('Tabs View', () => { await page.getByLabel(`${sineWaveGenerator.name} tab`, { exact: true }).click(); // ensure sine wave generator visible - expect(await page.locator('.c-plot').isVisible()).toBe(true); + await expect(page.locator('.c-plot')).toBeVisible(); // now select notebook and clear animation calls await page.getByLabel(`${notebook.name} tab`, { exact: true }).click(); diff --git a/e2e/tests/performance/tagging.perf.spec.js b/e2e/tests/performance/tagging.perf.spec.js index 495026f7a8..4fa19db2f9 100644 --- a/e2e/tests/performance/tagging.perf.spec.js +++ b/e2e/tests/performance/tagging.perf.spec.js @@ -84,7 +84,7 @@ test.describe('Plot Tagging Performance', () => { await setRealTimeMode(page); // Search for Science - await page.getByRole('searchbox', { name: 'Search Input' }); + await page.getByRole('searchbox', { name: 'Search Input' }).click(); await page.getByRole('searchbox', { name: 'Search Input' }).fill('sc'); // click on the search result diff --git a/e2e/tests/visual-a11y/components/header.visual.spec.js b/e2e/tests/visual-a11y/components/header.visual.spec.js index bbbdc5b4a6..f1dd4e0adc 100644 --- a/e2e/tests/visual-a11y/components/header.visual.spec.js +++ b/e2e/tests/visual-a11y/components/header.visual.spec.js @@ -89,6 +89,7 @@ test.describe('Visual - Header @a11y', () => { await percySnapshot(page, `Notebook Snapshot Show button (theme: '${theme}')`, { scope: header }); + await expect(page.getByLabel('Show Snapshots')).toBeVisible(); }); }); diff --git a/e2e/tests/visual-a11y/components/inspector.visual.spec.js b/e2e/tests/visual-a11y/components/inspector.visual.spec.js index bf573e5a51..5f1dd2be00 100644 --- a/e2e/tests/visual-a11y/components/inspector.visual.spec.js +++ b/e2e/tests/visual-a11y/components/inspector.visual.spec.js @@ -29,15 +29,14 @@ import { MISSION_TIME, VISUAL_FIXED_URL } from '../../../constants.js'; const inspectorPane = '.l-shell__pane-inspector'; test.describe('Visual - Inspector @ally @clock', () => { - test.beforeEach(async ({ page }) => { - await page.goto(VISUAL_FIXED_URL, { waitUntil: 'domcontentloaded' }); - }); test.use({ - storageState: 'test-data/overlay_plot_with_delay_storage.json', - clockOptions: { - now: MISSION_TIME, - shouldAdvanceTime: true - } + storageState: 'test-data/overlay_plot_with_delay_storage.json' + }); + + test.beforeEach(async ({ page }) => { + await page.clock.install({ time: MISSION_TIME }); + await page.clock.resume(); + await page.goto(VISUAL_FIXED_URL, { waitUntil: 'domcontentloaded' }); }); test('Inspector from overlay_plot_with_delay_storage @localStorage', async ({ page, theme }) => { diff --git a/e2e/tests/visual-a11y/components/timeConductor.visual.spec.js b/e2e/tests/visual-a11y/components/timeConductor.visual.spec.js index bd089bbb6c..e880bd6642 100644 --- a/e2e/tests/visual-a11y/components/timeConductor.visual.spec.js +++ b/e2e/tests/visual-a11y/components/timeConductor.visual.spec.js @@ -33,13 +33,9 @@ import { } from '../../../constants.js'; test.describe('Visual - Time Conductor', () => { - test.use({ - clockOptions: { - now: MISSION_TIME, - shouldAdvanceTime: false - } - }); test.beforeEach(async ({ page }) => { + await page.clock.install({ time: MISSION_TIME }); + await page.clock.pauseAt(MISSION_TIME); await page.goto('./', { waitUntil: 'domcontentloaded' }); }); @@ -87,7 +83,7 @@ test.describe('Visual - Time Conductor', () => { test( 'Visual - Time Conductor Axis Resized @clock @snapshot', { annotation: [{ type: 'issue', description: 'https://github.com/nasa/openmct/issues/7623' }] }, - async ({ page, tick }) => { + async ({ page }) => { const VISUAL_REALTIME_WITH_PANES = VISUAL_REALTIME_URL.replace( 'hideTree=true', 'hideTree=false' @@ -108,7 +104,7 @@ test.describe('Visual - Time Conductor', () => { await page.getByLabel('Collapse Browse Pane').click(); // manually tick the clock to trigger the resize / re-render - await tick(1000 * 2); + await page.clock.runFor(1000 * 2); const mask = []; diff --git a/e2e/tests/visual-a11y/components/timeConductor.visual.spec.js-snapshots/time-conductor-axis-resized-chrome-linux.png b/e2e/tests/visual-a11y/components/timeConductor.visual.spec.js-snapshots/time-conductor-axis-resized-chrome-linux.png index b86cc7eade..fd70070289 100644 Binary files a/e2e/tests/visual-a11y/components/timeConductor.visual.spec.js-snapshots/time-conductor-axis-resized-chrome-linux.png and b/e2e/tests/visual-a11y/components/timeConductor.visual.spec.js-snapshots/time-conductor-axis-resized-chrome-linux.png differ diff --git a/e2e/tests/visual-a11y/components/timeConductor.visual.spec.js-snapshots/time-conductor-realtime-chrome-linux.png b/e2e/tests/visual-a11y/components/timeConductor.visual.spec.js-snapshots/time-conductor-realtime-chrome-linux.png index b7b480324d..c89fc2df45 100644 Binary files a/e2e/tests/visual-a11y/components/timeConductor.visual.spec.js-snapshots/time-conductor-realtime-chrome-linux.png and b/e2e/tests/visual-a11y/components/timeConductor.visual.spec.js-snapshots/time-conductor-realtime-chrome-linux.png differ diff --git a/e2e/tests/visual-a11y/components/tree.visual.spec.js b/e2e/tests/visual-a11y/components/tree.visual.spec.js index 5eb2bba1ca..649b005bc4 100644 --- a/e2e/tests/visual-a11y/components/tree.visual.spec.js +++ b/e2e/tests/visual-a11y/components/tree.visual.spec.js @@ -22,16 +22,15 @@ import percySnapshot from '@percy/playwright'; -import { createDomainObjectWithDefaults, expandTreePaneItemByName } from '../../../appActions.js'; +import { createDomainObjectWithDefaults } from '../../../appActions.js'; +import { test } from '../../../avpFixtures.js'; import { VISUAL_FIXED_URL } from '../../../constants.js'; -import { test } from '../../../pluginFixtures.js'; //Declare the scope of the visual test const treePane = "[role=tree][aria-label='Main Tree']"; test.describe('Visual - Tree Pane', () => { - test('Tree pane in various states', async ({ page, theme, openmctConfig }) => { - const { myItemsFolderName } = openmctConfig; + test('Tree pane in various states', async ({ page, theme }) => { await page.goto(VISUAL_FIXED_URL, { waitUntil: 'domcontentloaded' }); //Open Tree @@ -69,28 +68,28 @@ test.describe('Visual - Tree Pane', () => { scope: treePane }); - await expandTreePaneItemByName(page, myItemsFolderName); + await page.getByLabel('Expand My Items folder').click(); await page.goto(foo.url); - await page.dragAndDrop('role=treeitem[name=/A Clock/]', '.c-object-view'); - await page.dragAndDrop('role=treeitem[name=/Z Clock/]', '.c-object-view'); + await page.getByLabel('Navigate to A Clock').dragTo(page.getByLabel('Object View')); + await page.getByLabel('Navigate to Z Clock').dragTo(page.getByLabel('Object View')); await page.goto(bar.url); - await page.dragAndDrop('role=treeitem[name=/A Clock/]', '.c-object-view'); - await page.dragAndDrop('role=treeitem[name=/Z Clock/]', '.c-object-view'); + await page.getByLabel('Navigate to A Clock').dragTo(page.getByLabel('Object View')); + await page.getByLabel('Navigate to Z Clock').dragTo(page.getByLabel('Object View')); await page.goto(baz.url); - await page.dragAndDrop('role=treeitem[name=/A Clock/]', '.c-object-view'); - await page.dragAndDrop('role=treeitem[name=/Z Clock/]', '.c-object-view'); + await page.getByLabel('Navigate to A Clock').dragTo(page.getByLabel('Object View')); + await page.getByLabel('Navigate to Z Clock').dragTo(page.getByLabel('Object View')); await percySnapshot(page, `Tree Pane w/ single level expanded (theme: ${theme})`, { scope: treePane }); - await expandTreePaneItemByName(page, foo.name); - await expandTreePaneItemByName(page, bar.name); - await expandTreePaneItemByName(page, baz.name); + await page.getByLabel(`Expand ${foo.name} folder`).click(); + await page.getByLabel(`Expand ${bar.name} folder`).click(); + await page.getByLabel(`Expand ${baz.name} folder`).click(); // eslint-disable-next-line playwright/no-wait-for-timeout - await page.waitForTimeout(1 * 1000); //https://github.com/nasa/openmct/issues/7059 + await page.waitForTimeout(3 * 1000); //https://github.com/nasa/openmct/issues/7059 await percySnapshot(page, `Tree Pane w/ multiple levels expanded (theme: ${theme})`, { scope: treePane diff --git a/e2e/tests/visual-a11y/controlledClock.visual.spec.js b/e2e/tests/visual-a11y/controlledClock.visual.spec.js index 8778713107..c175a776ab 100644 --- a/e2e/tests/visual-a11y/controlledClock.visual.spec.js +++ b/e2e/tests/visual-a11y/controlledClock.visual.spec.js @@ -32,18 +32,14 @@ import { expect, test } from '../../pluginFixtures.js'; test.describe('Visual - Controlled Clock @clock', () => { test.beforeEach(async ({ page }) => { + await page.clock.install({ time: MISSION_TIME }); await page.goto(VISUAL_FIXED_URL, { waitUntil: 'domcontentloaded' }); }); test.use({ - storageState: 'test-data/overlay_plot_with_delay_storage.json', - clockOptions: { - now: MISSION_TIME, - shouldAdvanceTime: false //Don't advance the clock - } + storageState: 'test-data/overlay_plot_with_delay_storage.json' }); test('Overlay Plot Loading Indicator @localStorage', async ({ page, theme }) => { - await page.goto(VISUAL_FIXED_URL, { waitUntil: 'domcontentloaded' }); await page .getByRole('gridcell', { hasText: 'Overlay Plot with 5s Delay Overlay Plot' }) .click(); diff --git a/e2e/tests/visual-a11y/displayLayout.visual.spec.js b/e2e/tests/visual-a11y/displayLayout.visual.spec.js index e937d7b7af..401c5b243c 100644 --- a/e2e/tests/visual-a11y/displayLayout.visual.spec.js +++ b/e2e/tests/visual-a11y/displayLayout.visual.spec.js @@ -27,13 +27,9 @@ import { MISSION_TIME, VISUAL_FIXED_URL } from '../../constants.js'; import { test } from '../../pluginFixtures.js'; test.describe('Visual - Display Layout @clock', () => { - test.use({ - clockOptions: { - now: MISSION_TIME, - shouldAdvanceTime: true - } - }); test.beforeEach(async ({ page }) => { + await page.clock.install({ time: MISSION_TIME }); + await page.clock.resume(); await page.goto(VISUAL_FIXED_URL, { waitUntil: 'domcontentloaded' }); const parentLayout = await createDomainObjectWithDefaults(page, { diff --git a/e2e/tests/visual-a11y/notebook.visual.spec.js b/e2e/tests/visual-a11y/notebook.visual.spec.js index ddf7da39c4..3532953fbf 100644 --- a/e2e/tests/visual-a11y/notebook.visual.spec.js +++ b/e2e/tests/visual-a11y/notebook.visual.spec.js @@ -22,7 +22,7 @@ import percySnapshot from '@percy/playwright'; -import { createDomainObjectWithDefaults, expandTreePaneItemByName } from '../../appActions.js'; +import { createDomainObjectWithDefaults } from '../../appActions.js'; import { expect, scanForA11yViolations, test } from '../../avpFixtures.js'; import { VISUAL_FIXED_URL } from '../../constants.js'; import { enterTextEntry, startAndAddRestrictedNotebookObject } from '../../helper/notebookUtils.js'; @@ -86,9 +86,7 @@ test.describe('Visual - Notebook @a11y', () => { name: 'Test Notebook' }); }); - test('Accepts dropped objects as embeds', async ({ page, theme, openmctConfig }) => { - const { myItemsFolderName } = openmctConfig; - + test('Accepts dropped objects as embeds', async ({ page, theme }) => { // Create Overlay Plot await createDomainObjectWithDefaults(page, { type: 'Overlay Plot', @@ -98,11 +96,13 @@ test.describe('Visual - Notebook @a11y', () => { //Open Tree to perform drag await page.getByRole('button', { name: 'Browse' }).click(); - await expandTreePaneItemByName(page, myItemsFolderName); + await page.getByLabel('Expand My Items folder').click(); await page.goto(notebook.url); - await page.dragAndDrop('role=treeitem[name=/Dropped Overlay Plot/]', '.c-notebook__drag-area'); + await page + .getByLabel('Navigate to Dropped Overlay Plot') + .dragTo(page.getByLabel('To start a new entry, click here or drag and drop any object')); await percySnapshot(page, `Notebook w/ dropped embed (theme: ${theme})`); }); diff --git a/e2e/tests/visual-a11y/planning-timelist.visual.spec.js b/e2e/tests/visual-a11y/planning-timelist.visual.spec.js index 2889b1a571..baf23a02a9 100644 --- a/e2e/tests/visual-a11y/planning-timelist.visual.spec.js +++ b/e2e/tests/visual-a11y/planning-timelist.visual.spec.js @@ -33,17 +33,12 @@ const examplePlanSmall1 = JSON.parse( fs.readFileSync(new URL('../../test-data/examplePlans/ExamplePlan_Small1.json', import.meta.url)) ); +const FIRST_ACTIVITY_SMALL_1 = getFirstActivity(examplePlanSmall1); + test.describe('Visual - Timelist progress bar @clock @a11y', () => { - const firstActivity = getFirstActivity(examplePlanSmall1); - - test.use({ - clockOptions: { - now: firstActivity.end + 10000, - shouldAdvanceTime: true - } - }); - test.beforeEach(async ({ page }) => { + await page.clock.install({ time: FIRST_ACTIVITY_SMALL_1.end + 10000 }); + await page.clock.resume(); await createTimelistWithPlanAndSetActivityInProgress(page, examplePlanSmall1); await page.getByLabel('Click to collapse items').click(); }); diff --git a/e2e/tests/visual-a11y/planning-timestrip.visual.spec.js b/e2e/tests/visual-a11y/planning-timestrip.visual.spec.js index 897fa60213..f0a6c9a90c 100644 --- a/e2e/tests/visual-a11y/planning-timestrip.visual.spec.js +++ b/e2e/tests/visual-a11y/planning-timestrip.visual.spec.js @@ -44,11 +44,13 @@ test.describe('Visual - Time Strip @a11y', () => { }); await createPlanFromJSON(page, { json: examplePlanSmall2, - parent: timeStrip.uuid + parent: timeStrip.uuid, + name: 'examplePlanSmall2' }); await createDomainObjectWithDefaults(page, { type: 'Sine Wave Generator', - parent: timeStrip.uuid + parent: timeStrip.uuid, + name: 'Sine Wave Generator' }); await page.goto(VISUAL_FIXED_URL, { waitUntil: 'domcontentloaded' }); diff --git a/e2e/tests/visual-a11y/planning-view.visual.spec.js b/e2e/tests/visual-a11y/planning-view.visual.spec.js index 7966fa8026..7ca1200c03 100644 --- a/e2e/tests/visual-a11y/planning-view.visual.spec.js +++ b/e2e/tests/visual-a11y/planning-view.visual.spec.js @@ -41,17 +41,12 @@ const examplePlanSmall2 = JSON.parse( fs.readFileSync(new URL('../../test-data/examplePlans/ExamplePlan_Small2.json', import.meta.url)) ); +const FIRST_ACTIVITY_SMALL_1 = getFirstActivity(examplePlanSmall1); + test.describe('Visual - Timelist progress bar @clock @a11y', () => { - const firstActivity = getFirstActivity(examplePlanSmall1); - - test.use({ - clockOptions: { - now: firstActivity.end + 10000, - shouldAdvanceTime: true - } - }); - test.beforeEach(async ({ page }) => { + await page.clock.install({ time: FIRST_ACTIVITY_SMALL_1.end + 10000 }); + await page.clock.resume(); await createTimelistWithPlanAndSetActivityInProgress(page, examplePlanSmall1); await page.getByLabel('Click to collapse items').click(); }); diff --git a/package-lock.json b/package-lock.json index 002dd4c0a9..b15818b907 100644 --- a/package-lock.json +++ b/package-lock.json @@ -37,7 +37,7 @@ "eslint-config-prettier": "9.1.0", "eslint-plugin-compat": "4.2.0", "eslint-plugin-no-unsanitized": "4.0.2", - "eslint-plugin-playwright": "0.12.0", + "eslint-plugin-playwright": "1.5.2", "eslint-plugin-prettier": "5.1.3", "eslint-plugin-simple-import-sort": "10.0.0", "eslint-plugin-unicorn": "49.0.0", @@ -104,9 +104,7 @@ "@axe-core/playwright": "4.8.5", "@percy/cli": "1.27.4", "@percy/playwright": "1.0.4", - "@playwright/test": "1.45.2", - "@types/sinonjs__fake-timers": "8.1.5", - "sinon": "17.0.0" + "@playwright/test": "1.45.2" } }, "e2e/node_modules/@percy/cli": { @@ -1582,50 +1580,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/@sinonjs/commons": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-3.0.1.tgz", - "integrity": "sha512-K3mCHKQ9sVh8o1C9cxkwxaOmXoAMlDxC1mYyHrjqOWEcBjYr76t96zL2zlj5dUGZ3HSw240X1qgH3Mjf1yJWpQ==", - "dev": true, - "dependencies": { - "type-detect": "4.0.8" - } - }, - "node_modules/@sinonjs/fake-timers": { - "version": "11.2.2", - "resolved": "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-11.2.2.tgz", - "integrity": "sha512-G2piCSxQ7oWOxwGSAyFHfPIsyeJGXYtc6mFbnFA+kRXkiEnTl8c/8jul2S329iFBnDI9HGoeWWAZvuvOkZccgw==", - "dev": true, - "dependencies": { - "@sinonjs/commons": "^3.0.0" - } - }, - "node_modules/@sinonjs/samsam": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/@sinonjs/samsam/-/samsam-8.0.0.tgz", - "integrity": "sha512-Bp8KUVlLp8ibJZrnvq2foVhP0IVX2CIprMJPK0vqGqgrDa0OHVKeZyBykqskkrdxV6yKBPmGasO8LVjAKR3Gew==", - "dev": true, - "dependencies": { - "@sinonjs/commons": "^2.0.0", - "lodash.get": "^4.4.2", - "type-detect": "^4.0.8" - } - }, - "node_modules/@sinonjs/samsam/node_modules/@sinonjs/commons": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-2.0.0.tgz", - "integrity": "sha512-uLa0j859mMrg2slwQYdO/AkrOfmH+X6LTVmNTS9CqexuE2IvVORIkSpJLqePAbEnKJ77aMmCwr1NUZ57120Xcg==", - "dev": true, - "dependencies": { - "type-detect": "4.0.8" - } - }, - "node_modules/@sinonjs/text-encoding": { - "version": "0.7.2", - "resolved": "https://registry.npmjs.org/@sinonjs/text-encoding/-/text-encoding-0.7.2.tgz", - "integrity": "sha512-sXXKG+uL9IrKqViTtao2Ws6dy0znu9sOaP1di/jKGW1M6VssO8vlpXCQcpZ+jisQ1tTFAC5Jo/EOzFbggBagFQ==", - "dev": true - }, "node_modules/@socket.io/component-emitter": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/@socket.io/component-emitter/-/component-emitter-3.1.0.tgz", @@ -1906,12 +1860,6 @@ "@types/node": "*" } }, - "node_modules/@types/sinonjs__fake-timers": { - "version": "8.1.5", - "resolved": "https://registry.npmjs.org/@types/sinonjs__fake-timers/-/sinonjs__fake-timers-8.1.5.tgz", - "integrity": "sha512-mQkU2jY8jJEF7YHjHvsQO8+3ughTL1mcnn96igfhONmR+fUPSKIkefQYpSe8bsly2Ep7oQbn/6VG5/9/0qcArQ==", - "dev": true - }, "node_modules/@types/sockjs": { "version": "0.3.36", "resolved": "https://registry.npmjs.org/@types/sockjs/-/sockjs-0.3.36.tgz", @@ -4235,15 +4183,6 @@ "integrity": "sha512-uJaamHkagcZtHPqCIHZxnFrXlunQXgBOsZSUOWwFw31QJCAbyTBoHMW75YOTur5ZNx8pIeAKgf6GWIgaqqiLhA==", "dev": true }, - "node_modules/diff": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/diff/-/diff-5.2.0.tgz", - "integrity": "sha512-uIFDxqpRZGZ6ThOk84hEfqWoHx2devRFvpTZcTHur85vImfaxUbTW9Ryh4CpCuDnToOP1CEtXKIgytHBPVff5A==", - "dev": true, - "engines": { - "node": ">=0.3.1" - } - }, "node_modules/dir-glob": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz", @@ -4756,13 +4695,22 @@ } }, "node_modules/eslint-plugin-playwright": { - "version": "0.12.0", - "resolved": "https://registry.npmjs.org/eslint-plugin-playwright/-/eslint-plugin-playwright-0.12.0.tgz", - "integrity": "sha512-KXuzQjVzca5irMT/7rvzJKsVDGbQr43oQPc8i+SLEBqmfrTxlwMwRqfv9vtZqh4hpU0jmrnA/EOfwtls+5QC1w==", + "version": "1.5.2", + "resolved": "https://registry.npmjs.org/eslint-plugin-playwright/-/eslint-plugin-playwright-1.5.2.tgz", + "integrity": "sha512-TMzLrLGQMccngU8GogtzIc9u5RzXGnfsQEUjLfEfshINuVR2fS4SHfDtU7xYP90Vwm5vflHECf610KTdGvO53w==", "dev": true, + "workspaces": [ + "examples" + ], + "dependencies": { + "globals": "^13.23.0" + }, + "engines": { + "node": ">=16.6.0" + }, "peerDependencies": { - "eslint": ">=7", - "eslint-plugin-jest": ">=24" + "eslint": ">=8.40.0", + "eslint-plugin-jest": ">=25" }, "peerDependenciesMeta": { "eslint-plugin-jest": { @@ -4770,6 +4718,33 @@ } } }, + "node_modules/eslint-plugin-playwright/node_modules/globals": { + "version": "13.24.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-13.24.0.tgz", + "integrity": "sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ==", + "dev": true, + "dependencies": { + "type-fest": "^0.20.2" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint-plugin-playwright/node_modules/type-fest": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", + "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/eslint-plugin-prettier": { "version": "5.1.3", "resolved": "https://registry.npmjs.org/eslint-plugin-prettier/-/eslint-plugin-prettier-5.1.3.tgz", @@ -7250,12 +7225,6 @@ "graceful-fs": "^4.1.6" } }, - "node_modules/just-extend": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/just-extend/-/just-extend-6.2.0.tgz", - "integrity": "sha512-cYofQu2Xpom82S6qD778jBDpwvvy39s1l/hrYij2u9AMdQcGRpaBu6kY4mVhuno5kJVi1DAz4aiphA2WI1/OAw==", - "dev": true - }, "node_modules/karma": { "version": "6.4.2", "resolved": "https://registry.npmjs.org/karma/-/karma-6.4.2.tgz", @@ -7636,12 +7605,6 @@ "integrity": "sha512-uHaJFihxmJcEX3kT4I23ABqKKalJ/zDrDg0lsFtc1h+3uw49SIJ5beyhx5ExVRti3AvKoOJngIj7xz3oylPdWQ==", "dev": true }, - "node_modules/lodash.get": { - "version": "4.4.2", - "resolved": "https://registry.npmjs.org/lodash.get/-/lodash.get-4.4.2.tgz", - "integrity": "sha512-z+Uw/vLuy6gQe8cfaFWD7p0wVv8fJl3mbzXh33RS+0oW2wvUqiRXiQ69gLWSLpgB5/6sU+r6BlQR0MBILadqTQ==", - "dev": true - }, "node_modules/lodash.memoize": { "version": "4.1.2", "resolved": "https://registry.npmjs.org/lodash.memoize/-/lodash.memoize-4.1.2.tgz", @@ -8073,19 +8036,6 @@ "integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==", "dev": true }, - "node_modules/nise": { - "version": "5.1.9", - "resolved": "https://registry.npmjs.org/nise/-/nise-5.1.9.tgz", - "integrity": "sha512-qOnoujW4SV6e40dYxJOb3uvuoPHtmLzIk4TFo+j0jPJoC+5Z9xja5qH5JZobEPsa8+YYphMrOSwnrshEhG2qww==", - "dev": true, - "dependencies": { - "@sinonjs/commons": "^3.0.0", - "@sinonjs/fake-timers": "^11.2.2", - "@sinonjs/text-encoding": "^0.7.2", - "just-extend": "^6.2.0", - "path-to-regexp": "^6.2.1" - } - }, "node_modules/node-fetch": { "version": "2.7.0", "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz", @@ -10200,45 +10150,6 @@ "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", "dev": true }, - "node_modules/sinon": { - "version": "17.0.0", - "resolved": "https://registry.npmjs.org/sinon/-/sinon-17.0.0.tgz", - "integrity": "sha512-p4lJiYKBoOEVUxxVIC9H1MM2znG1/c8gud++I2BauJA5hsz7hHsst35eurNWXTusBsIq66FzOQbZ/uMdpvbPIQ==", - "dev": true, - "dependencies": { - "@sinonjs/commons": "^3.0.0", - "@sinonjs/fake-timers": "^11.2.2", - "@sinonjs/samsam": "^8.0.0", - "diff": "^5.1.0", - "nise": "^5.1.5", - "supports-color": "^7.2.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/sinon" - } - }, - "node_modules/sinon/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/sinon/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/slash": { "version": "5.1.0", "resolved": "https://registry.npmjs.org/slash/-/slash-5.1.0.tgz", @@ -10944,15 +10855,6 @@ "node": ">= 0.8.0" } }, - "node_modules/type-detect": { - "version": "4.0.8", - "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz", - "integrity": "sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==", - "dev": true, - "engines": { - "node": ">=4" - } - }, "node_modules/type-fest": { "version": "0.8.1", "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.8.1.tgz", diff --git a/package.json b/package.json index 14f0b37fa3..654ddac942 100644 --- a/package.json +++ b/package.json @@ -40,7 +40,7 @@ "eslint-config-prettier": "9.1.0", "eslint-plugin-compat": "4.2.0", "eslint-plugin-no-unsanitized": "4.0.2", - "eslint-plugin-playwright": "0.12.0", + "eslint-plugin-playwright": "1.5.2", "eslint-plugin-prettier": "5.1.3", "eslint-plugin-simple-import-sort": "10.0.0", "eslint-plugin-unicorn": "49.0.0", @@ -116,14 +116,13 @@ "test:e2e:a11y": "npm test --workspace e2e -- --config=playwright-visual-a11y.config.js --project=chrome --grep @a11y", "test:e2e:mobile": "npm test --workspace e2e -- --config=playwright-mobile.config.js", "test:e2e:couchdb": "npm test --workspace e2e -- --config=playwright-ci.config.js --project=chrome --grep @couchdb --workers=1", - "test:e2e:stable": "npm test --workspace e2e -- --config=playwright-ci.config.js --project=chrome --grep-invert \"@unstable|@couchdb|@generatedata\"", - "test:e2e:unstable": "npm test --workspace e2e -- --config=playwright-ci.config.js --project=chrome --grep @unstable", + "test:e2e:ci": "npm test --workspace e2e -- --config=playwright-ci.config.js --project=chrome --grep-invert \"@couchdb|@generatedata\"", "test:e2e:local": "npm test --workspace e2e -- --config=playwright-local.config.js --project=chrome", "test:e2e:generatedata": "npm test --workspace e2e -- --config=playwright-ci.config.js --project=chrome --grep @generatedata", "test:e2e:checksnapshots": "npm test --workspace e2e -- --config=playwright-ci.config.js --project=chrome --grep @snapshot --retries=0", "test:e2e:updatesnapshots": "npm test --workspace e2e -- --config=playwright-ci.config.js --project=chrome --grep @snapshot --update-snapshots", - "test:e2e:visual:ci": "npm run test:visual --workspace e2e -- --config .percy.ci.yml --partial -- npx playwright test --config=playwright-visual-a11y.config.js --project=chrome --grep-invert @unstable", - "test:e2e:visual:full": "npm run test:visual --workspace e2e -- --config .percy.nightly.yml -- npx playwright test --config=playwright-visual-a11y.config.js --grep-invert @unstable", + "test:e2e:visual:ci": "npm run test:visual --workspace e2e -- --config .percy.ci.yml --partial -- npx playwright test --config=playwright-visual-a11y.config.js --project=chrome", + "test:e2e:visual:full": "npm run test:visual --workspace e2e -- --config .percy.nightly.yml -- npx playwright test --config=playwright-visual-a11y.config.js", "test:e2e:full": "npm test --workspace e2e -- --config=playwright-ci.config.js --grep-invert @couchdb", "test:e2e:watch": "npm test --workspace e2e -- --ui --config=playwright-watch.config.js", "test:perf:contract": "npm test --workspace e2e -- --config=playwright-performance-dev.config.js", @@ -133,7 +132,7 @@ "update-copyright-date": "npm run update-about-dialog-copyright && grep -lr --null --include=*.{js,scss,vue,ts,sh,html,md,frag} 'Copyright (c) 20' . | xargs -r0 perl -pi -e 's/Copyright\\s\\(c\\)\\s20\\d\\d\\-20\\d\\d/Copyright \\(c\\)\\ 2014\\-2024/gm'", "cov:e2e:report": "nyc report --reporter=lcovonly --report-dir=./coverage/e2e", "cov:e2e:full:publish": "codecov --disable=gcov -f ./coverage/e2e/lcov.info -F e2e-full", - "cov:e2e:stable:publish": "codecov --disable=gcov -f ./coverage/e2e/lcov.info -F e2e-stable", + "cov:e2e:ci:publish": "codecov --disable=gcov -f ./coverage/e2e/lcov.info -F e2e-ci", "cov:unit:publish": "codecov --disable=gcov -f ./coverage/unit/lcov.info -F unit", "prepare": "npm run build:prod && npx tsc" }, diff --git a/src/api/forms/components/FormProperties.vue b/src/api/forms/components/FormProperties.vue index f4ac6b23af..1e4bdf30bb 100644 --- a/src/api/forms/components/FormProperties.vue +++ b/src/api/forms/components/FormProperties.vue @@ -115,7 +115,7 @@ export default { return this.model.buttons.submit.label; } - return 'OK'; + return 'Ok'; }, cancelLabel() { if (this.model.buttons && this.model.buttons.cancel && this.model.buttons.cancel.label) { diff --git a/src/api/forms/components/controls/FileInput.vue b/src/api/forms/components/controls/FileInput.vue index 911fdb945a..dd27d6ea3a 100644 --- a/src/api/forms/components/controls/FileInput.vue +++ b/src/api/forms/components/controls/FileInput.vue @@ -29,6 +29,7 @@ type="file" :accept="acceptableFileTypes" style="display: none" + aria-labelledby="fileSelect" />