Compare commits

...

64 Commits

Author SHA1 Message Date
45cbf514f2 docs: update release docs 2024-02-28 13:25:37 -08:00
8090e71110 docs(WIP): update release documentation 2024-02-22 11:37:46 -08:00
dc9bd8bcb1 Remove all logic around auto centering and scrolling the timelist (#7474) 2024-02-22 17:56:54 +00:00
29d83e9c6d fix(#7456): allow children of Overlay Plots to be removed (#7516)
* fix(#7456): check if object is an identifier

* refactor: use v-show, remove unnecessary deep copy, set yAxes array to empty in a way that preserves reactivity

* refactor: use ESM exports

* refactor: use ESM imports

* test(e2e): add test for element item removal from overlay plot

* a11y: add accessible name for object labels

* refactor: move overlayPlot creation to beforeAll, use getByLabel
2024-02-21 16:07:48 -08:00
6393a77c19 [Mobile] Add Global Search to Mobile (#7477)
* Add status area back to mobile

* Make search results responsive to width

* Make clear search button always visible, regardless of hover

* Make clear search button visible, and fix weird margin in top left corner

* Fix input width, add logic to make close button work properly, fix margin on results so there is room for close button, fix various landscape mode issues

* update mobile testing

* Fix dropdown sizes, remove shadows and corners to make it look less like a popup and more full screen

* Add animation and persist search bar in mobile

* Fix linting issues

* Fix padding in Desktop

* fix padding in status area

* fix bad merge

* lint fixes

* fix bad merge... again

* again

* fixes to mobile

* update tests

* lint fix

* test fixes

---------

Co-authored-by: John Hill <john.c.hill@nasa.gov>
2024-02-21 15:29:38 -08:00
6bbabf9c45 chore(deps-dev): bump vue from 3.3.8 to 3.4.19 (#7511)
Bumps [vue](https://github.com/vuejs/core) from 3.3.8 to 3.4.19.
- [Release notes](https://github.com/vuejs/core/releases)
- [Changelog](https://github.com/vuejs/core/blob/main/CHANGELOG.md)
- [Commits](https://github.com/vuejs/core/compare/v3.3.8...v3.4.19)

---
updated-dependencies:
- dependency-name: vue
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Jesse Mazzella <ozyx@users.noreply.github.com>
2024-02-21 15:13:02 -08:00
f18d1d2a51 Add Collapse to Recent Objects (#7502)
* initial functional implementation

* removing vestigial code

* Fix styles for button bar

* Remove bound for :persist-position

* Revert "Remove bound for :persist-position"

This reverts commit ce2ea422d7.

* test(e2e): modify existing test to verify collapse/expand recent objects pane

---------

Co-authored-by: Jamie V <jamie.j.vigliotta@nasa.gov>
Co-authored-by: Jesse Mazzella <jesse.d.mazzella@nasa.gov>
2024-02-21 21:28:29 +00:00
5894c66df1 docs(release.yml): make plural (#7517) 2024-02-20 14:58:44 -08:00
6ff8c42041 docs: Update release.yml and docs (#7514)
Attempt one
2024-02-20 14:29:00 -08:00
9870a6bc9c Check role when receiving status updates in the Operator Status Indicator (#7509)
* check role when receiving status updates

* pass role to get status
2024-02-16 21:45:04 +00:00
317ea8c275 docs: Add openmct type hints (#7247)
* Add setAssetPath method to MCT type information

* Add TypeRegistry to OpenMCT typedef
2024-02-16 16:56:13 +00:00
bc36a93b9b chore: bump version to 4.0.0-next (#7506)
Bump to 4.0.0
2024-02-15 13:49:04 -08:00
847232d64b style: ensure legacy indicators align with Vue indicators (#7458)
* style: ensure legacy indicators align with Vue indicators

* fix: move logic to `vueWrapHtmlElement` and use `u-contents` class
2024-02-15 20:22:52 +01:00
4fbccd4c91 Ensure object specific actions load in previews (#7504)
* add actions after element has had chance to render

* add test

* remove test.only
2024-02-15 18:00:04 +00:00
cd560bceed Bypass cache/dirty when canceling transaction (#7503)
* force remote/non-cached when canceling transaction

* add test

* update unit test
2024-02-15 09:26:45 -08:00
e08633214e Misc memory leak fixes (#7224)
* fix(leak): remove font listeners

* fix(leak): release componentInstance

* fix(perf): remove unused emit

* fix(perf): only emit if tickWidth changes

* fix: warnings and undefined keys

* fix: remove unused bind

* fix: restore MctTicks component
2024-02-14 23:49:02 +00:00
a9ad0bf38a When plan view is used in a gantt chart, handle the domain object differently. (#7473)
Rename for readability. Track plan changes if object is a plan.
2024-02-14 22:32:50 +00:00
5f8d6899d2 Plot legends expand by default when enabled (#7453)
* expanded legend showing, but malformed

* fix legends

* add e2e test and aria labels for controls

* fix tests

* remove focused test

* make plot legend items dynamic

* expand legend immediately when changing default

* Ensure stacked plots show cumulative legend (#7481)

* simplify config loading logic

* wip

* fixed stacked plot legend issue

* fix legend

* remove console.debugs

* remove extraneous prop

* add test

* fix legend

* use props
2024-02-07 18:35:19 +00:00
cd6adbadde Upgrade prettier and eslint compatibility libraries to latest (#7478)
* chore: upgrade prettier and eslint libraries to latest

- upgrade prettier
- upgrade eslint
- upgrade eslint-plugin-prettier
- upgrade eslint-config-prettier

* chore: run lint:fix

* chore: add `prettier-eslint` devDependency

- The `prettier-eslint` vscode plugin sinc v6.0.0 no longer provides this package so we must install it as a devDependency so that autoformat works.

* chore: add recommended extensions file for vscode users

* Update extensions.json

* Revert "Update extensions.json"

This reverts commit 942f341a75.

---------

Co-authored-by: John Hill <john.c.hill@nasa.gov>
2024-02-07 00:16:22 +00:00
539b43325a test(e2e): add e2e and visual tests for Mission Status (plus a11y) (#7462)
* feat: enable mission status in example user

* test: add initial missionStatus suite

* test(WIP): mission status e2e suite

* test(e2e): add e2e and visual tests for mission status + a11y

* test(a11y): scan for a11y violations

* a11y: remove labels for non-interactive elements
2024-02-06 21:44:01 +00:00
eeb8e9704b Ensure previews work for plots (#7459)
* fix large view in tree

* remove existing view concept

* fix plots in overlays

* remove debug and actually remove overlays when dismissed

* add test

* improve tests

* move test
2024-02-06 08:16:31 +01:00
0aceb4b590 Filter values as a string not an object (#7448)
* Push the value of a property to the activity as a string if it is not undefined.

* Add documentation for sourceMap filterMetadata

* Allow . for filtering. Check for null values
2024-02-05 18:38:37 +00:00
82fa4c1597 feat: Mission Status for Situational Awareness (#7418)
* refactor: `UserIndicator` use vue component directly

* style(WIP): filler styles for user-indicator

* feat(WIP): working on mission status indicators

* feat: support mission statuses

* feat(WIP): can display mission statuses now

* feat(WIP): add composables and dynamically calculate popup position

* feat(WIP): dismissible popup, use moar compositionAPI

* Closes #7420
- Styling and markup for mission status control panel.
- Tweaks and additions to some common style elements.

* feat: set/unset mission status for role

* refactor: rename some functions

* feat: more renaming, get mission role statuses working

* refactor: more method renaming

* fix: remove dupe method

* feat: hook up event listeners

* refactor: convert to CompositionAPI and listen to events

* fix: add that back in, woops

* test: fix some existing tests

* lint: words for the word god

* refactor: rename

* fix: setting mission statuses

* style: fix go styling

* style: add mission status button

* refactor: rename `MissionRole` -> `MissionAction`

* test: fix most existing tests

* test: remove integration tests already covered by e2e

- These tests are going to be wonky since they depend on the View. Any unit tests depending on Vue / the View will become increasingly volatile over time as we migrate more of the app into the main Vue app. Since these LOC are already covered by e2e, I'm going to remove them. We will need to move towards a more component / Vue-friendly testing framework to stabilize all of this.

* docs: add documentation

* refactor: rename

* fix: a comma

* refactor: a word

* fix: emit parameter format

* fix: correct emit for `missionStatusActionChange`

---------

Co-authored-by: Charles Hacskaylo <charles.f.hacskaylo@nasa.gov>
Co-authored-by: John Hill <john.c.hill@nasa.gov>
2024-02-02 22:50:16 +00:00
ee5081f807 test(visual): add unclipped activity names visual tests + a11y fixes (#7454)
* test: add unclipped activity names visual tests + a11y fixes

* lint: add additional dictionaries
2024-02-02 19:47:52 +00:00
3cbaa7bf07 Have countdowns in the Timelist use a - symbol (#7452)
* fix issue

* add test

* remove debugger

* expanded legend showing, but malformed

* Revert "expanded legend showing, but malformed"

This reverts commit b954f00257.
2024-02-02 09:37:37 +01:00
18e4b9da65 Add Expanded view for Time List (#7378)
* Add activity states domain object and interceptor to auto create one

* Add activity state inspector option

* Only save status if we have a unique ids for activities

* Include the id in the activity properties

* Don't show activity state section in the inspector if multiple activities are selected

* Display activity properties when an activity row is selected in the timelist

* Add compact view for timelist

* Add inspector configuration for compact view

* Set colors based on time relation of activity

* Use activity id as key if it is available

* Ensure the correct option is selected for activity states

* Closes #7377
- Markup and CSS sanding and polishing.
- Still WIP!

* Closes #7377
- Markup and CSS sanding and polishing.
- Still WIP!

* Add status label

* Rename to Expanded view and isExpanded as properties. Add display style dropdown configuration in the inspector.

* Refactor activity selection. Display activity properties

* Closes #7377
- Label formatting Todo notes about states.
- Computed values and `v-ifs` added to control display for progress pie and countdown 'hero'.
- Still WIP!

* Closes #7377
- Add svg icons and some stubbed in logic.
- Still WIP!

* Remove activity states plugin. Move the activity states interceptor to the plan plugin.

* Change activity states interceptor parameters to options

* Rename constants

* Fix activity states test

* Addresses review comments making code more readable.

* Closes #7377
- Significant adds for large Time List element styling for activity states.
- `$color*` Time List-related theme constants remapped and significantly enhanced.
- Code cleanup and removal of stubbed-in SCSS vars.

* Closes #7377
- Unit testing and colors in Snow in progress.
- Fixed erroneous checkin in ExpandedViewItem.vue.

* Remove ExpandedView component and pull the ExpandedViewItem up to the top level.
Same for ListView, pulling the ListItem up one level.

* Fix sorting for compact view.
Hardcode options for switching compact/expanded views.

* Closes #7377
- Snow Time List colors finalized and smoke tested.
- New graphic SVG for skipped activity.
- Added aria labels to SVG graphics.

* Closes #7377
- Fixed div with no class.

* Add e2e test for activity states feature.

* Address review comments. Rename variables, documentation.

* No shallow copy

* Merge updates to activity-state

* Sync with activity states PR

* Draft of progress-pie

* - Add `s-selected` styling for Expanded Time List elements.

* Add 2 new date formats

* Look and feel enhancements for pie, zero duration events and start and end time formats

* Fix pie show/hide condition

* Final touches to the pie and labels

* Refactor label logic

* Closes #7377
- Added `sweep-hand` animation element to progress pie graphic SVG.

* Remove use of ListView - no point passing arrays around since we are already using sortedItems and itemProperties for expandedViewItems

* We addded a new column for duration and changed the previous duration to countdown. This required adjustment of the test

* Fix expanded view for timelist tests

* Closes #7377
- Fixed display logic for inferred execution states.

* Closes #7377
- Fixed a bug that threw console errors when a value was undefined.

* Optimize rendering of timelist activities

* Remove focused test

* Address review comments

* Remove reactive selection for plan activities

* destructure props into individual item properties for render performance benefits

* Use local variables and remove JSON utility methods

* Change cancelled to skipped

* Focus the activity tab when shown

* Fix label updates

* Add countup to cspell

* Remove progress pie due to licensing unknowns

---------

Co-authored-by: Charles Hacskaylo <charlesh88@gmail.com>
Co-authored-by: Charles Hacskaylo <charles.f.hacskaylo@nasa.gov>
2024-01-30 16:30:57 -08:00
d42aa545bb Add support for multiple CouchDB databases, multiple namespaces, and readOnly configurations (#7413)
* two namespaces appear

* works with two databases

* try to batch requests

* fix indicators

* add option to omit root for myitems

* ready for review

* ready for review

* ready for review

* typo in README

* spelling

* spelling

* update readme

* fix tests

* Update src/plugins/persistence/couch/README.md

Co-authored-by: Jesse Mazzella <ozyx@users.noreply.github.com>

* remove omitRoot

* remove omitRoot

* fix my items to check for namespace when intercepting

* update readme

---------

Co-authored-by: Jesse Mazzella <ozyx@users.noreply.github.com>
Co-authored-by: Andrew Henry <akhenry@gmail.com>
2024-01-30 15:10:31 -08:00
69b81c00ca [e2e] Steps to reduce flakiness in test reporting (#7436)
* flakefinder gha

* Update e2e-flakefinder.yml

* driveby

* skip visual

* first attempt at sharding with circle

* Updated config.yml

* fixes

* missing quote

* re-enable old jobs and update to 7x

* max failures

* destructure the npm script

* missing quote

* revert

* uncomment and re-add 7 parallel

* add unit tests

* add p flag

* skip the flaky test
2024-01-30 14:21:52 -08:00
068ac4899d Fix constrast for accessibility (#7315)
* Closes #7304
- Change colors to increase contrast.
- New base level theme color var: `$colorBodyFgSubtle`.
- Minor CSS cleanups.
- WARNING: this appears to have added a regression in selects
that colors the arrow black in Espresso.

* Closes #7304
- Fix dropdown arrow colors, whew.
- Normalize font sizes in Status area.
- More color changes for contrast, including new theme constants.
- TODO: compare and sync Espresso with other themes.
- TODO: check for regressions!

* disable ruleset

* Closes #7304
- Normalize font sizes in multiple spots.
- More color changes for contrast, including more new theme constants.
- TODO: compare and sync Espresso with other themes.
- TODO: check for regressions!

* Closes #7304
- Reorganize CSS files for more uniformity.

* Closes #7304
- CSS normalized across all themes via Google Sheet at https://docs.google.com/spreadsheets/d/1SEEtuNSq6I7gvVHKpHW8_fp8Ltc-HOAWxrSAkUzS6Kw/edit?usp=sharing

* Closes #7304
- Color tweaks, normalization.

* Closes #7304
- Color tweaks, normalization.
- Search layout, contrast and font-size improvements.
- Added '+' icons to collapsed pane buttons.

* Closes #7304
- Shell head layout improvements.

* Update ColorKey for Take Snapshot Failures and Opacity labels. Also fix create menu

* Closes #7410
- CHERRY PICK FROM event-colors-7410.
- Event display approach modified to include background color.
- Theme colors modified and constrast verified via Wave a11y browser plugin.

* Closes #7304
- Set back to install Espresso theme by default.

* temporarily skip

* remove comment

* lint

* Update default colors

* update snapshot

* missed

---------

Co-authored-by: John Hill <john.c.hill@nasa.gov>
Co-authored-by: Shefali Joshi <simplyrender@gmail.com>
2024-01-29 21:30:55 -08:00
f8d936a834 Use different root for renderWhenVisible (#7415)
* attempt to fix

* reenable test

* going to revert most of this, but works

* slowly reverting changes

* further reversions to the mean

* reversion to the mean

* revert

* change to use openmct element

* reference issue

* reference issue

---------

Co-authored-by: John Hill <john.c.hill@nasa.gov>
2024-01-29 15:33:47 -08:00
5c21c34568 Support telemetry batching and move WebSocket handling to worker (#7391)
* Support subscription batching from API, Tables, and Plots

* Added batching worker

* Added configurable batch size and throttling rate

* Support batch size based throttling

* Default to latest strategy

* Don't hide original error

* Added copyright statement

* Renamed BatchingWebSocketProvider to BatchingWebSocket

* Adding docs

* renamed class. changed throttling strategy to be driven by the main thread

* Renamed classes

* Added more documentation

* Fixed broken tests

* Addressed review comments

* Clean up and reconnect on websocket close

* Better management of subscription strategies

* Add tests to catch edge cases where two subscribers request different strategies

* Ensure callbacks are invoked with telemetry in the requested format

* Remove console out. Oops

* Fix linting errors
2024-01-29 15:17:55 -08:00
0eea2e0bbc chore(deps-dev): bump marked from 11.1.0 to 11.2.0 (#7427)
Bumps [marked](https://github.com/markedjs/marked) from 11.1.0 to 11.2.0.
- [Release notes](https://github.com/markedjs/marked/releases)
- [Changelog](https://github.com/markedjs/marked/blob/master/.releaserc.json)
- [Commits](https://github.com/markedjs/marked/compare/v11.1.0...v11.2.0)

---
updated-dependencies:
- dependency-name: marked
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: John Hill <john.c.hill@nasa.gov>
2024-01-29 12:32:06 -08:00
61acc91200 chore(deps-dev): bump sass-loader from 13.3.2 to 14.0.0 (#7428)
Bumps [sass-loader](https://github.com/webpack-contrib/sass-loader) from 13.3.2 to 14.0.0.
- [Release notes](https://github.com/webpack-contrib/sass-loader/releases)
- [Changelog](https://github.com/webpack-contrib/sass-loader/blob/master/CHANGELOG.md)
- [Commits](https://github.com/webpack-contrib/sass-loader/compare/v13.3.2...v14.0.0)

---
updated-dependencies:
- dependency-name: sass-loader
  dependency-type: direct:development
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: John Hill <john.c.hill@nasa.gov>
2024-01-29 12:31:36 -08:00
a52982d2bf chore(deps-dev): bump moment from 2.29.4 to 2.30.1 (#7425)
Bumps [moment](https://github.com/moment/moment) from 2.29.4 to 2.30.1.
- [Changelog](https://github.com/moment/moment/blob/develop/CHANGELOG.md)
- [Commits](https://github.com/moment/moment/compare/2.29.4...2.30.1)

---
updated-dependencies:
- dependency-name: moment
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-01-29 12:27:25 -08:00
1d40b134b6 Make Tabs eager loading configurable but default to false (#7199)
* convert tabs plugin to use es6 import/export

* default of eager load is false but configurable

* change true/false select to toggleSwitch

* add and clean up unit tests

* Update test

---------

Co-authored-by: John Hill <john.c.hill@nasa.gov>
2024-01-29 12:08:38 -08:00
735c8236e5 Skip some tests, fix a mislabeled test, and add default condition for tabs (#7422)
* wrong format

* skip the flake for now

* Add Tabs test

* one more flake
2024-01-28 16:28:49 -08:00
dc5a3236b3 Activity state display for plans in Gantt and Time list views (#7370)
* Add activity states domain object and interceptor to auto create one

* Add activity state inspector option

* Only save status if we have a unique ids for activities

* Include the id in the activity properties

* Don't show activity state section in the inspector if multiple activities are selected

* Display activity properties when an activity row is selected in the timelist

* Use activity id as key if it is available

* Ensure the correct option is selected for activity states

* Add status label

* Refactor activity selection. Display activity properties

* Remove activity states plugin. Move the activity states interceptor to the plan plugin.

* Change activity states interceptor parameters to options

* Rename constants

* Fix activity states test

* Add e2e test for activity states feature.

* Address review comments. Rename variables, documentation.

* No shallow copy

* Suppress lint warning for conditionals

* Remove check for abort controller

* Move classes to components

* number primitive

* Closes #7369
- WIP tweaks to simplify the Inspector view.

* Ensure 'notStarted' is the default state for activities

* Remove extra quotes

* Closes #7369
- Mod to `s-selected` styling to allow selection visiblity on Time List rows.

* Use generated key for vue

* Fix e2e tests

* Fix timelist test

---------

Co-authored-by: Charles Hacskaylo <charlesh88@gmail.com>
Co-authored-by: John Hill <john.c.hill@nasa.gov>
2024-01-28 15:46:10 -08:00
60e1eeba8e Add filtering by metadata (#7388)
* Add filtering by metadata. Add new sourceMap property to get a list of properties for metadata filtering.

* Change filter label names

* Add aria-labels

* Closes #7389
- Added a "No filters applied" message for both input areas.
- Added additional detail about how it works in the hint text visible while editing.

* Restore valid state if there is an error

* Fix linting error

* Tests for filtering by metadata

---------

Co-authored-by: Charles Hacskaylo <charlesh88@gmail.com>
2024-01-28 18:04:52 +00:00
1fc6056c51 Set disabled items to use disabled property (#7342)
* Closes #7322
- New CSS for `aria-disabled = true` property.
- Changed multiple items to use aria-disabled instead of .disabled, including:
  - Action menu
  - Super menu
  - Notebook drag area
- Tree item style modded to only italicize when is-navigated and is being edited.

* Closes #7322
- New CSS for `aria-disabled = true` property.
- Changed multiple items to use aria-disabled instead of .disabled, including:
  - Action menu
  - Super menu
  - Notebook drag area
- Tree item style modded to only italicize when is-navigated and is being edited.
- Create button sets itself to `disabled` when the editor is in use.

* Closes #7322
- Create button now _actually_ sets itself to `aria-disabled` when the editor is in use.
- CSS removes selector for `.is-editing`.

* fix conflict

---------

Co-authored-by: John Hill <john.c.hill@nasa.gov>
2024-01-26 14:55:13 -08:00
b9df97e2bc Table performance paging (#7399)
* dereactifying the row before passing it to the commponent

* debouncin

* i mean... throttle

* initial

* UI functionality, switching between modes, prevention of export in performance mode, respect size option in swgs

* added limit maintenance in table row collectins, autoscroll respecting sort order

* updating the logic to work correctly :)

* added handling for overflow rows, this way if an object is removed, we can go back to the most recent rows for all remaining items and repopulate the table if necessary

* removing debug row numbers

* Closes #7268
- Layout and style sanding and polishing.
- Added title to button.
- More direct button labeling.

* Closes #7268
Partially closes #7147
- Removed footer hover behavior: table footer now always visible.
- Tweaks to style, margin etc. to make footer more compact.

* moved row limiting out of table row collections and into telemetry collections, table row collections will only limit what they return in getRows, handling sorting when in different modes

* have swgs return enough data to fill the requested bounds

* support minmax in swgs

* using undefined for more clarity

* clearing up boolean typo

* Address lint fixes

* removing autoscroll for descending, it is not necessary

* update snapshots

* lint

---------

Co-authored-by: Charles Hacskaylo <charlesh88@gmail.com>
Co-authored-by: John Hill <john.c.hill@nasa.gov>
2024-01-26 13:24:24 -08:00
b985619d16 Table CPU Performance Improvements (#7392)
* dereactifying the row before passing it to the commponent

* debouncin

* i mean... throttle
2024-01-26 10:40:16 -08:00
3e31bbef97 Get actions collection on Preview Container update (#7385)
* Get actions collection on Preview Container update

* Added fixme and link to initial ticket

* Stubbed out preview mode e2e test

* Lint fix

---------

Co-authored-by: Jesse Mazzella <ozyx@users.noreply.github.com>
2024-01-26 17:35:52 +00:00
3e5ada8f5f Fix actions menu on display layout alphanumerics (#7414)
* remove errant action

* add e2e test
2024-01-25 16:26:03 +01:00
2b2c74da9c New action to reload an individual view and all of its children (#7362)
* add reload action plugin

* checking for domain object before reloading

* check if objects are equal before refreshing

* add test

* lint

* change to label

* ensure object styles are initialized

* resubscribe to staleness too

* add better labels for tabels

* ensure tab uses exact for label now due to table aria changes

* fix table tests

* make tabs exact

* update conflicts

---------

Co-authored-by: Jesse Mazzella <ozyx@users.noreply.github.com>
2024-01-25 05:36:44 +00:00
450cab428f move performance tests to GHA (#7412)
* move performance tests to GHA

* no need for chrome beta

* Add baseline imagery test

* skip flaky app

* lint
2024-01-24 21:26:48 -08:00
0340fe18fa Change Imagery positional freshness label from 'POS' to 'ROV' (#7409)
Closes #7404
- Change 'POS' to 'ROV'.
2024-01-25 05:18:29 +00:00
114864429a feat(#7394): Incorporate Status Indicators into the main Vue app (#7395)
* feat(IndicatorAPI): accept Vue components

- Adds a new property to Indicators, `component`, which is a synchronous or asynchronous Vue component.
- Adds `wrapHtmlElement` utility function to create anonymous Vue components out of `HTMLElement`s (for backwards compatibility)
- Refactors StatusIndicators.vue to use dynamic components, allowing us to dynamically render indicators (and keep it all within Vue's ecosystem).

* refactor(indicators): use dynamic Vue components instead of `mount()`

- Refactors some indicators to use Vue components directly as async components

* refactor: use Vue reactivity for timestamps in clock indicator

* fix(test): fix unit tests and remove some console logs

* test(e2e): stabilize ladSet e2e test

* test: mix in some Vue indicators in indicatorSpec

* refactor: cleanup variable names

* docs: update IndicatorAPI docs

* fix(e2e): wait for async status bar components to load before snapshot

* a11y(e2e): add aria-labels and wait for status bar to load

* test(e2e): add exact: true

* fix: initializing indicators

* fix(typo): uhhh.. how did that get there? O_o

* fix: use synchronous components for default indicators

* test: clean up, remove unnecessary `nextTick()`s

* test: remove more `nextTick()`s

* refactor: lint:fix

* fix: `on` -> `off`

* test(e2e): stabilize tabs test

* test(e2e): attempt to stabilize limit lines tests with `toHaveCount()` assertion
2024-01-23 23:15:22 +00:00
4cf63062c0 Mct7367-tests (#7387)
* refactor(ExportAsJSONAction): use private methods

* refactor: remove unnecessary webpack alias

* refactor: lint

* fix: tests for `ExportAsJSONAction`

* test: stabilize `InspectorStylesSpec` tests

* docs: fix jsdocs

* chore: remove dead / redundant code

* refactor(LocalStorageObjectProvider): use `getItem()` and `setItem()`

* refactor(ExportAsJSONAction): use `Promise.all` where applicable

* refactor(MenuAPI): one-liner

* feat: add percentage ProgressBar to ExportAsJSONAction

* fix(ProgressBar.vue): v-if conditionals

* test(fix): update mockLocalStorage

* test: fix locators

* test: remove unneeded awaits

* fix: example imagery urls (moved after NASA wordpress migration)

* Revert "refactor(LocalStorageObjectProvider): use `getItem()` and `setItem()`"

This reverts commit 4f8403adab.

* test(e2e): fix logPlot test

* Revert "Revert "refactor(LocalStorageObjectProvider): use `getItem()` and `setItem()`""

This reverts commit 0de66401cd.

* test(e2e): remove waitForNavigations

* driveby and fixes

* aria improvement

* getting tests back oline

* more tests

* add last test

* Add a11y

* lint

* lint

* driveby

* review comments

* driveby rename

* fix selectors and break up test suites

* add test for snapshot in header

* last lint fixes

* stable

---------

Co-authored-by: Jesse Mazzella <jesse.d.mazzella@nasa.gov>
2024-01-22 21:41:56 -08:00
43ae3cf655 Provide renderWhenVisible to LadTableSets (#7384)
* Provide renderWhenVisible for LadTables

* fix open in new tab

* add test for open in new tab

* fix test to include renderWhenVisible

* refactor test

* fix tests

* fix tests

* having timing issues now
2024-01-19 09:47:44 +01:00
01434ff2d5 feat(#7367): ProgressBar for ExportAsJSONAction (#7374)
* refactor(ExportAsJSONAction): use private methods

* refactor: remove unnecessary webpack alias

* refactor: lint

* fix: tests for `ExportAsJSONAction`

* test: stabilize `InspectorStylesSpec` tests

* docs: fix jsdocs

* chore: remove dead / redundant code

* refactor(LocalStorageObjectProvider): use `getItem()` and `setItem()`

* refactor(ExportAsJSONAction): use `Promise.all` where applicable

* refactor(MenuAPI): one-liner

* feat: add percentage ProgressBar to ExportAsJSONAction

* fix(ProgressBar.vue): v-if conditionals

* test(fix): update mockLocalStorage

* test: fix locators

* test: remove unneeded awaits

* fix: example imagery urls (moved after NASA wordpress migration)

* Revert "refactor(LocalStorageObjectProvider): use `getItem()` and `setItem()`"

This reverts commit 4f8403adab.

* test(e2e): fix logPlot test

* Revert "Revert "refactor(LocalStorageObjectProvider): use `getItem()` and `setItem()`""

This reverts commit 0de66401cd.

* test(e2e): remove waitForNavigations
2024-01-18 12:21:36 +01:00
70f5ba9ca8 Clear active role if no longer in available roles (#7345)
* if role is removed from user, reprompt

* add some basic user login tests

* add more robust user tests

* add more robust user tests

* resolve PR comments

* setup event listener earlier
2024-01-16 17:57:57 +01:00
11f3ce9470 fix(#7055): Expand on image with double click (#7308)
* Add double click event to expand image view

* Add performance tests for large view and close
button

* Add example imagery in flexible layout to test double-click functionality

* Remove click event listener in ImageryView.vue

* Move test to 'Example Imagery in Flexible layout'

* Refactor exampleImagery.e2e.spec.js

* Remove unnecessary code for opening and closing
large view in imagery.contract.per.spec.js

* Replace pageSelector with getByRole on double-click image test

* Add role and aria-label to focused image

* Remove unnecessary code and role attribute from ImageryView.vue

---------

Co-authored-by: Jesse Mazzella <ozyx@users.noreply.github.com>
2024-01-14 15:55:11 -08:00
6ce340cebd [e2e] Update remaining tests and add missing comparison coverage (#7363) 2024-01-11 14:47:00 -08:00
6fd7b6f7a3 Add right click actions on imagery to allow high resolution download (#7371)
* action stubs in place

* first draft of actions

* delete copy

* add test

* remove console.debug
2024-01-11 19:57:04 +01:00
3ff2f029eb chore: update copyright end year to 2024 (#7364) 2024-01-09 13:31:51 -08:00
67a1094a1f Timelist centering algorithm change and duration formatting change (#7194)
* Change the centering algorithm for timelist
Make duration formatting better

* test: add time list countdown and countup test

* test: respond to comments

* chore: lint fix

* fix: lint errors and visual suite failures

* Change parameters to options object

* Fix regression with auto scroll. Improve code readability

* Add defaults for getPreciseDuration options object

* Defaults for options

* Add missing await

---------

Co-authored-by: Jesse Mazzella <jesse.d.mazzella@nasa.gov>
2024-01-08 23:14:33 +00:00
64d4ddd80e Set location hash if query parameters or path have changed (#7306)
* refactor to es6 class

* change URL on path or params change

* add test for url

* put into edit mode for test

* es6 module export

* a11y: add `status` role to clock component

* a11y: add label to overlay

* a11y: update roles for search results

* a11y: add `dialog` role and label for PreviewContainer

* refactor(e2e): get rid of a bunch of `page.locator()`s

* refactor(e2e): spruce up locators

* test: fix unit tests

* fix tests with new aria labels

* fix tests with new aria labels

* fix tests with new aria labels

---------

Co-authored-by: Jesse Mazzella <jesse.d.mazzella@nasa.gov>
2024-01-08 20:29:01 +01:00
dfba4e23c5 fix(#7338): Persisted styles correctly apply to StackedPlotItems on mount (#7341)
* refactor: use `Plot` component directly in `StackedPlotItem` template

* test(e2e): fix style test for stackedplot

* test(e2e): add annotation back in

* test: fix unit tests

* refactor: tidy up

* fix: move const, remove eslint ignore

* fix: apply staleness styling properly

* refactor: remove unused data()

* refactor: remove unused `isMissing`

* refactor: remove debug statements
2024-01-08 10:19:41 -08:00
70de7363d8 [Flexible Layouts] Flexible Layout styling fixes (#7319) 2024-01-03 17:11:35 -08:00
6f3bb5fc6f chore(deps-dev): bump @vue/compiler-sfc from 3.3.10 to 3.4.3 (#7332)
Bumps [@vue/compiler-sfc](https://github.com/vuejs/core/tree/HEAD/packages/compiler-sfc) from 3.3.10 to 3.4.3.
- [Release notes](https://github.com/vuejs/core/releases)
- [Changelog](https://github.com/vuejs/core/blob/main/CHANGELOG.md)
- [Commits](https://github.com/vuejs/core/commits/v3.4.3/packages/compiler-sfc)

---
updated-dependencies:
- dependency-name: "@vue/compiler-sfc"
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-01-02 17:46:40 +00:00
b220c2bad5 chore(deps-dev): bump typescript from 5.2.2 to 5.3.3 (#7335)
Bumps [typescript](https://github.com/Microsoft/TypeScript) from 5.2.2 to 5.3.3.
- [Release notes](https://github.com/Microsoft/TypeScript/releases)
- [Commits](https://github.com/Microsoft/TypeScript/compare/v5.2.2...v5.3.3)

---
updated-dependencies:
- dependency-name: typescript
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-01-02 09:04:52 -08:00
74f30789ca chore(deps-dev): bump @babel/eslint-parser from 7.22.5 to 7.23.3 (#7232)
Bumps [@babel/eslint-parser](https://github.com/babel/babel/tree/HEAD/eslint/babel-eslint-parser) from 7.22.5 to 7.23.3.
- [Release notes](https://github.com/babel/babel/releases)
- [Changelog](https://github.com/babel/babel/blob/main/CHANGELOG.md)
- [Commits](https://github.com/babel/babel/commits/v7.23.3/eslint/babel-eslint-parser)

---
updated-dependencies:
- dependency-name: "@babel/eslint-parser"
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-01-02 08:22:06 -08:00
fce98a1c47 refactor: migrate to ESM (#7331)
* refactor: move package.json to type: module

this is where the fun begins

* chore: move webpack common and prod to esm

* chore: move webpack to esm, eslint to explicit cjs

* refactor: migrate all files to esm

* style: lint

* refactor: begin moving karma to cjs, use dynamic esm import

* refactor: move index-test to cjs

* refactor: begin moving e2e to ESM

this was manual. I'm committing this because I'm about to try the `cjstoesm` tool

* refactor: move all to esm

* fix: make all e2e tests use .js imports

* refactor: begin moving exports to esm

* refactor: use URL transforms instead of __dirname

* fix: use libraryExport: default to properly handle openmct

* fix: export all playwright configs as modules

* refactor: move all instances of __dirname to import.meta.url

* refactor: lint, drop unnecessary URL call

* fix: use correct URL path on helper/addNoneditableObject.js

* fix: more incorrect URL resolve issues

* fix: parse json after reading it
2024-01-02 07:24:22 -08:00
68e60e332e fix: cleanup from #7029 (#7328)
* refactor: fix issues arose from #7029

* refactor: move replaceAll -> replace in makeKeyString

* fix: move order of escape to backslash first

* style: lint
2023-12-28 12:39:28 -08:00
969 changed files with 12400 additions and 4907 deletions

View File

@ -5,20 +5,20 @@ executors:
- image: mcr.microsoft.com/playwright:v1.39.0-focal - image: mcr.microsoft.com/playwright:v1.39.0-focal
environment: environment:
NODE_ENV: development # Needed to ensure 'dist' folder created and devDependencies installed NODE_ENV: development # Needed to ensure 'dist' folder created and devDependencies installed
PERCY_POSTINSTALL_BROWSER: 'true' # Needed to store the percy browser in cache deps PERCY_POSTINSTALL_BROWSER: "true" # Needed to store the percy browser in cache deps
PERCY_LOGLEVEL: 'debug' # Enable DEBUG level logging for Percy (Issue: https://github.com/nasa/openmct/issues/5742) PERCY_LOGLEVEL: "debug" # Enable DEBUG level logging for Percy (Issue: https://github.com/nasa/openmct/issues/5742)
ubuntu: ubuntu:
machine: machine:
image: ubuntu-2204:current image: ubuntu-2204:current
docker_layer_caching: true docker_layer_caching: true
parameters: parameters:
BUST_CACHE: BUST_CACHE:
description: 'Set this with the CircleCI UI Trigger Workflow button (boolean = true) to bust the cache!' description: "Set this with the CircleCI UI Trigger Workflow button (boolean = true) to bust the cache!"
default: false default: false
type: boolean type: boolean
commands: commands:
build_and_install: build_and_install:
description: 'All steps used to build and install. Will use cache if found' description: "All steps used to build and install. Will use cache if found"
parameters: parameters:
node-version: node-version:
type: string type: string
@ -30,7 +30,7 @@ commands:
node-version: << parameters.node-version >> node-version: << parameters.node-version >>
- run: npm install --no-audit --progress=false - run: npm install --no-audit --progress=false
restore_cache_cmd: restore_cache_cmd:
description: 'Custom command for restoring cache with the ability to bust cache. When BUST_CACHE is set to true, jobs will not restore cache' description: "Custom command for restoring cache with the ability to bust cache. When BUST_CACHE is set to true, jobs will not restore cache"
parameters: parameters:
node-version: node-version:
type: string type: string
@ -42,7 +42,7 @@ commands:
- restore_cache: - restore_cache:
key: deps--{{ arch }}--{{ .Branch }}--<< parameters.node-version >>--{{ checksum "package.json" }}-{{ checksum ".circleci/config.yml" }} key: deps--{{ arch }}--{{ .Branch }}--<< parameters.node-version >>--{{ checksum "package.json" }}-{{ checksum ".circleci/config.yml" }}
save_cache_cmd: save_cache_cmd:
description: 'Custom command for saving cache.' description: "Custom command for saving cache."
parameters: parameters:
node-version: node-version:
type: string type: string
@ -53,7 +53,7 @@ commands:
- ~/.npm - ~/.npm
- node_modules - node_modules
generate_and_store_version_and_filesystem_artifacts: generate_and_store_version_and_filesystem_artifacts:
description: 'Track important packages and files' description: "Track important packages and files"
steps: steps:
- run: | - run: |
[[ $EUID -ne 0 ]] && (sudo mkdir -p /tmp/artifacts && sudo chmod 777 /tmp/artifacts) || (mkdir -p /tmp/artifacts && chmod 777 /tmp/artifacts) [[ $EUID -ne 0 ]] && (sudo mkdir -p /tmp/artifacts && sudo chmod 777 /tmp/artifacts) || (mkdir -p /tmp/artifacts && chmod 777 /tmp/artifacts)
@ -64,7 +64,7 @@ commands:
- store_artifacts: - store_artifacts:
path: /tmp/artifacts/ path: /tmp/artifacts/
generate_e2e_code_cov_report: generate_e2e_code_cov_report:
description: 'Generate e2e code coverage artifacts and publish to codecov.io. Needed to that we can ignore the exit code status of the npm run test' description: "Generate e2e code coverage artifacts and publish to codecov.io. Needed to that we can ignore the exit code status of the npm run test"
parameters: parameters:
suite: suite:
type: string type: string
@ -105,7 +105,11 @@ jobs:
node-version: <<parameters.node-version>> node-version: <<parameters.node-version>>
- browser-tools/install-chrome: - browser-tools/install-chrome:
replace-existing: false replace-existing: false
- run: npm run test - run:
command: |
mkdir -p dist/reports/tests/
TESTFILES=$(circleci tests glob "src/**/*Spec.js")
echo "$TESTFILES" | circleci tests run --command="xargs npm run test" --verbose
- run: npm run cov:unit:publish - run: npm run cov:unit:publish
- save_cache_cmd: - save_cache_cmd:
node-version: <<parameters.node-version>> node-version: <<parameters.node-version>>
@ -123,16 +127,20 @@ jobs:
suite: #stable or full suite: #stable or full
type: string type: string
executor: pw-focal-development executor: pw-focal-development
parallelism: 6 parallelism: 7
steps: steps:
- build_and_install: - build_and_install:
node-version: lts/hydrogen node-version: lts/hydrogen
- when: #Only install chrome-beta when running the 'full' suite to save $$$ - when: #Only install chrome-beta when running the 'full' suite to save $$$
condition: condition:
equal: ['full', <<parameters.suite>>] equal: ["full", <<parameters.suite>>]
steps: steps:
- run: npx playwright install chrome-beta - run: npx playwright install chrome-beta
- run: SHARD="$((${CIRCLE_NODE_INDEX}+1))"; npm run test:e2e:<<parameters.suite>> -- --shard=${SHARD}/${CIRCLE_NODE_TOTAL} - run:
command: |
mkdir test-results
TESTFILES=$(circleci tests glob "e2e/**/*.spec.js")
echo "$TESTFILES" | circleci tests run --command="xargs npm run test:e2e:<<parameters.suite>>" --verbose --split-by=timings
- when: - when:
condition: condition:
equal: [42, 42] # Always run codecov reports regardless of test failure https://discuss.circleci.com/t/make-custom-command-run-always-with-when-always/38957/2 equal: [42, 42] # Always run codecov reports regardless of test failure https://discuss.circleci.com/t/make-custom-command-run-always-with-when-always/38957/2
@ -152,6 +160,31 @@ jobs:
equal: [42, 42] # Always generate version artifacts regardless of test failure https://discuss.circleci.com/t/make-custom-command-run-always-with-when-always/38957/2 equal: [42, 42] # Always generate version artifacts regardless of test failure https://discuss.circleci.com/t/make-custom-command-run-always-with-when-always/38957/2
steps: steps:
- generate_and_store_version_and_filesystem_artifacts - generate_and_store_version_and_filesystem_artifacts
e2e-mobile:
executor: pw-focal-development
steps:
- build_and_install:
node-version: lts/hydrogen
- run: npm run test:e2e:mobile
- when:
condition:
equal: [42, 42] # Always run codecov reports regardless of test failure https://discuss.circleci.com/t/make-custom-command-run-always-with-when-always/38957/2
steps:
- generate_e2e_code_cov_report:
suite: full
- store_test_results:
path: test-results/results.xml
- store_artifacts:
path: test-results
- store_artifacts:
path: coverage
- store_artifacts:
path: html-test-results
- when:
condition:
equal: [42, 42] # Always generate version artifacts regardless of test failure https://discuss.circleci.com/t/make-custom-command-run-always-with-when-always/38957/2
steps:
- generate_and_store_version_and_filesystem_artifacts
e2e-couchdb: e2e-couchdb:
executor: ubuntu executor: ubuntu
steps: steps:
@ -239,6 +272,7 @@ jobs:
equal: [42, 42] # Always generate version artifacts regardless of test failure https://discuss.circleci.com/t/make-custom-command-run-always-with-when-always/38957/2 equal: [42, 42] # Always generate version artifacts regardless of test failure https://discuss.circleci.com/t/make-custom-command-run-always-with-when-always/38957/2
steps: steps:
- generate_and_store_version_and_filesystem_artifacts - generate_and_store_version_and_filesystem_artifacts
workflows: workflows:
overall-circleci-commit-status: #These jobs run on every commit overall-circleci-commit-status: #These jobs run on every commit
jobs: jobs:
@ -251,10 +285,9 @@ workflows:
- e2e-test: - e2e-test:
name: e2e-stable name: e2e-stable
suite: stable suite: stable
- mem-test - e2e-mobile
- perf-test
- visual-a11y-tests: - visual-a11y-tests:
name: visual-test-ci name: visual-a11y-test-ci
suite: ci suite: ci
the-nightly: #These jobs do not run on PRs, but against master at night the-nightly: #These jobs do not run on PRs, but against master at night
@ -270,15 +303,16 @@ workflows:
- e2e-test: - e2e-test:
name: e2e-full-nightly name: e2e-full-nightly
suite: full suite: full
- mem-test - e2e-mobile
- perf-test - perf-test
- mem-test
- visual-a11y-tests: - visual-a11y-tests:
name: visual-test-nightly name: visual-a11y-test-nightly
suite: full suite: full
- e2e-couchdb - e2e-couchdb
triggers: triggers:
- schedule: - schedule:
cron: '0 0 * * *' cron: "0 0 * * *"
filters: filters:
branches: branches:
only: only:

View File

@ -43,7 +43,6 @@
"sharded", "sharded",
"perfromance", "perfromance",
"MMOC", "MMOC",
"deploysentinel",
"codegen", "codegen",
"Unfortuantely", "Unfortuantely",
"viewports", "viewports",
@ -491,9 +490,16 @@
"oger", "oger",
"lcovonly", "lcovonly",
"gcov", "gcov",
"WCAG" "WCAG",
"stackedplot",
"Andale",
"unnormalized",
"checksnapshots",
"specced",
"composables",
"countup"
], ],
"dictionaries": ["npm", "softwareTerms", "node", "html", "css", "bash", "en_US"], "dictionaries": ["npm", "softwareTerms", "node", "html", "css", "bash", "en_US", "en-gb", "misc"],
"ignorePaths": [ "ignorePaths": [
"package.json", "package.json",
"dist/**", "dist/**",

View File

@ -4,6 +4,10 @@
# Requires Git > 2.23 # Requires Git > 2.23
# See https://git-scm.com/docs/git-blame#Documentation/git-blame.txt---ignore-revs-fileltfilegt # See https://git-scm.com/docs/git-blame#Documentation/git-blame.txt---ignore-revs-fileltfilegt
# vue-eslint update 2019
14a0f84c1bcd56886d7c9e4e6afa8f7d292734e5
# eslint changes 2022
d80b6923541704ab925abf0047cbbc58735c27e2
# Copyright year update 2022 # Copyright year update 2022
4a9744e916d24122a81092f6b7950054048ba860 4a9744e916d24122a81092f6b7950054048ba860
# Copyright year update 2023 # Copyright year update 2023

View File

@ -8,7 +8,7 @@ Closes <!--- Insert Issue Number(s) this PR addresses. Start by typing # will op
* [ ] Have you followed the guidelines in our [Contributing document](https://github.com/nasa/openmct/blob/master/CONTRIBUTING.md)? * [ ] Have you followed the guidelines in our [Contributing document](https://github.com/nasa/openmct/blob/master/CONTRIBUTING.md)?
* [ ] Have you checked to ensure there aren't other open [Pull Requests](https://github.com/nasa/openmct/pulls) for the same update/change? * [ ] Have you checked to ensure there aren't other open [Pull Requests](https://github.com/nasa/openmct/pulls) for the same update/change?
* [ ] Is this change backwards compatible? For example, developers won't need to change how they are calling the API or how they've extended core plugins such as Tables or Plots. * [ ] Is this a notable change that will require a special callout in the release notes [Notable Change](../docs/src/process/release.md) ? For example, will this break compatibility with existing APIs or projects which source these plugins?
### Author Checklist ### Author Checklist

5
.github/release.yml vendored
View File

@ -1,5 +1,8 @@
changelog: changelog:
categories: categories:
- title: 💥 Notable Changes
labels:
- notable_change
- title: 🏕 Features - title: 🏕 Features
labels: labels:
- type:feature - type:feature
@ -20,4 +23,4 @@ changelog:
- dependencies - dependencies
- title: 🐛 Bug Fixes - title: 🐛 Bug Fixes
labels: labels:
- '*' - "*"

View File

@ -47,9 +47,8 @@ jobs:
bash src/plugins/persistence/couch/setup-couchdb.sh bash src/plugins/persistence/couch/setup-couchdb.sh
bash src/plugins/persistence/couch/replace-localstorage-with-couchdb-indexhtml.sh bash src/plugins/persistence/couch/replace-localstorage-with-couchdb-indexhtml.sh
- name: Run CouchDB Tests and publish to deploysentinel - name: Run CouchDB Tests
env: env:
DEPLOYSENTINEL_API_KEY: ${{ secrets.DEPLOYSENTINEL_API_KEY }}
COMMIT_INFO_SHA: ${{github.event.pull_request.head.sha }} COMMIT_INFO_SHA: ${{github.event.pull_request.head.sha }}
run: npm run test:e2e:couchdb run: npm run test:e2e:couchdb

61
.github/workflows/e2e-flakefinder.yml vendored Normal file
View File

@ -0,0 +1,61 @@
name: 'pr:e2e:flakefinder'
on:
push:
branches: master
workflow_dispatch:
pull_request:
types:
- labeled
- opened
schedule:
- cron: '0 0 * * *'
jobs:
e2e-flakefinder:
if: contains(github.event.pull_request.labels.*.name, 'pr:e2e:flakefinder') || github.event_name == 'schedule' || github.event_name == 'workflow_dispatch' || github.event.action == 'opened'
runs-on: ubuntu-latest
timeout-minutes: 120
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 'lts/hydrogen'
- name: Cache NPM dependencies
uses: actions/cache@v3
with:
path: ~/.npm
key: ${{ runner.os }}-node-${{ hashFiles('**/package.json') }}
restore-keys: |
${{ runner.os }}-node-
- run: npx playwright@1.39.0 install
- run: npm install --cache ~/.npm --no-audit --progress=false
- name: Run E2E Tests (Repeated 10 Times)
run: npm run test:e2e:stable -- --retries=0 --repeat-each=10 --max-failures=50
- name: Archive test results
if: success() || failure()
uses: actions/upload-artifact@v3
with:
path: test-results
- name: Remove pr:e2e:flakefinder label (if present)
if: always()
uses: actions/github-script@v6
with:
script: |
const { owner, repo, number } = context.issue;
const labelToRemove = 'pr:e2e:flakefinder';
try {
await github.rest.issues.removeLabel({
owner,
repo,
issue_number: number,
name: labelToRemove
});
} catch (error) {
core.warning(`Failed to remove ' + labelToRemove + ' label: ${error.message}`);
}

58
.github/workflows/e2e-perf.yml vendored Normal file
View File

@ -0,0 +1,58 @@
name: 'e2e-perf'
on:
push:
branches: master
workflow_dispatch:
pull_request:
types:
- labeled
- opened
schedule:
- cron: '0 0 * * *'
jobs:
e2e-full:
if: contains(github.event.pull_request.labels.*.name, 'pr:e2e:perf') || github.event_name == 'schedule' || github.event_name == 'workflow_dispatch'
runs-on: ubuntu-latest
timeout-minutes: 60
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 'lts/hydrogen'
- name: Cache NPM dependencies
uses: actions/cache@v3
with:
path: ~/.npm
key: ${{ runner.os }}-node-${{ hashFiles('**/package.json') }}
restore-keys: |
${{ runner.os }}-node-
- run: npx playwright@1.39.0 install
- run: npm install --cache ~/.npm --no-audit --progress=false
- run: npm run test:perf:localhost
- run: npm run test:perf:contract
- run: npm run test:perf:memory
- name: Archive test results
if: success() || failure()
uses: actions/upload-artifact@v3
with:
path: test-results
- name: Remove pr:e2e:perf label (if present)
if: always()
uses: actions/github-script@v6
with:
script: |
const { owner, repo, number } = context.issue;
const labelToRemove = 'pr:e2e:perf';
try {
await github.rest.issues.removeLabel({
owner,
repo,
issue_number: number,
name: labelToRemove
});
} catch (error) {
core.warning(`Failed to remove ' + labelToRemove + ' label: ${error.message}`);
}

3
.gitignore vendored
View File

@ -15,6 +15,9 @@
*.idea *.idea
*.iml *.iml
# VSCode
.vscode/settings.json
# Build output # Build output
target target
dist dist

14
.vscode/extensions.json vendored Normal file
View File

@ -0,0 +1,14 @@
{
// See https://go.microsoft.com/fwlink/?LinkId=827846 to learn about workspace recommendations.
// Extension identifier format: ${publisher}.${name}. Example: vscode.csharp
// List of extensions which should be recommended for users of this workspace.
"recommendations": [
"Vue.volar",
"Vue.vscode-typescript-vue-plugin",
"dbaeumer.vscode-eslint",
"rvest.vs-code-prettier-eslint"
],
// List of extensions recommended by VS Code that should not be recommended for users of this workspace.
"unwantedRecommendations": ["octref.vetur"]
}

View File

@ -1,5 +1,3 @@
/* global __dirname module */
/* /*
This is the OpenMCT common webpack file. It is imported by the other three webpack configurations: This is the OpenMCT common webpack file. It is imported by the other three webpack configurations:
- webpack.prod.js - the production configuration for OpenMCT (default) - webpack.prod.js - the production configuration for OpenMCT (default)
@ -8,27 +6,28 @@ This is the OpenMCT common webpack file. It is imported by the other three webpa
There are separate npm scripts to use these configurations, though simply running `npm install` There are separate npm scripts to use these configurations, though simply running `npm install`
will use the default production configuration. will use the default production configuration.
*/ */
const path = require('path'); import { execSync } from 'node:child_process';
const packageDefinition = require('../package.json'); import fs from 'node:fs';
const CopyWebpackPlugin = require('copy-webpack-plugin'); import path from 'node:path';
const webpack = require('webpack'); import { fileURLToPath } from 'node:url';
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
const { VueLoaderPlugin } = require('vue-loader'); import CopyWebpackPlugin from 'copy-webpack-plugin';
import MiniCssExtractPlugin from 'mini-css-extract-plugin';
import { VueLoaderPlugin } from 'vue-loader';
import webpack from 'webpack';
let gitRevision = 'error-retrieving-revision'; let gitRevision = 'error-retrieving-revision';
let gitBranch = 'error-retrieving-branch'; let gitBranch = 'error-retrieving-branch';
const packageDefinition = JSON.parse(fs.readFileSync(new URL('../package.json', import.meta.url)));
try { try {
gitRevision = require('child_process').execSync('git rev-parse HEAD').toString().trim(); gitRevision = execSync('git rev-parse HEAD').toString().trim();
gitBranch = require('child_process') gitBranch = execSync('git rev-parse --abbrev-ref HEAD').toString().trim();
.execSync('git rev-parse --abbrev-ref HEAD')
.toString()
.trim();
} catch (err) { } catch (err) {
console.warn(err); console.warn(err);
} }
const projectRootDir = path.resolve(__dirname, '..'); const projectRootDir = fileURLToPath(new URL('../', import.meta.url));
/** @type {import('webpack').Configuration} */ /** @type {import('webpack').Configuration} */
const config = { const config = {
@ -56,6 +55,7 @@ const config = {
filename: '[name].js', filename: '[name].js',
path: path.resolve(projectRootDir, 'dist'), path: path.resolve(projectRootDir, 'dist'),
library: 'openmct', library: 'openmct',
libraryExport: 'default',
libraryTarget: 'umd', libraryTarget: 'umd',
publicPath: '', publicPath: '',
hashFunction: 'xxhash64', hashFunction: 'xxhash64',
@ -65,7 +65,6 @@ const config = {
alias: { alias: {
'@': path.join(projectRootDir, 'src'), '@': path.join(projectRootDir, 'src'),
legacyRegistry: path.join(projectRootDir, 'src/legacyRegistry'), legacyRegistry: path.join(projectRootDir, 'src/legacyRegistry'),
saveAs: 'file-saver/src/FileSaver.js',
csv: 'comma-separated-values', csv: 'comma-separated-values',
EventEmitter: 'eventemitter3', EventEmitter: 'eventemitter3',
bourbon: 'bourbon.scss', bourbon: 'bourbon.scss',
@ -183,4 +182,4 @@ const config = {
} }
}; };
module.exports = config; export default config;

View File

@ -1,12 +1,10 @@
/* global module */
/* /*
This file extends the webpack.dev.js config to add babel istanbul coverage. This file extends the webpack.dev.js config to add babel istanbul coverage.
OpenMCT Continuous Integration servers use this configuration to add code coverage OpenMCT Continuous Integration servers use this configuration to add code coverage
information to pull requests. information to pull requests.
*/ */
const config = require('./webpack.dev'); import config from './webpack.dev.js';
// eslint-disable-next-line no-undef // eslint-disable-next-line no-undef
const CI = process.env.CI === 'true'; const CI = process.env.CI === 'true';
@ -34,4 +32,4 @@ config.module.rules.push({
} }
}); });
module.exports = config; export default config;

View File

@ -1,18 +1,16 @@
/* global __dirname module */
/* /*
This configuration should be used for development purposes. It contains full source map, a This configuration should be used for development purposes. It contains full source map, a
devServer (which be invoked using by `npm start`), and a non-minified Vue.js distribution. devServer (which be invoked using by `npm start`), and a non-minified Vue.js distribution.
If OpenMCT is to be used for a production server, use webpack.prod.js instead. If OpenMCT is to be used for a production server, use webpack.prod.js instead.
*/ */
const path = require('path'); import path from 'path';
const webpack = require('webpack'); import webpack from 'webpack';
const { merge } = require('webpack-merge'); import { merge } from 'webpack-merge';
import { fileURLToPath } from 'node:url';
const common = require('./webpack.common'); import common from './webpack.common.js';
const projectRootDir = path.resolve(__dirname, '..');
module.exports = merge(common, { export default merge(common, {
mode: 'development', mode: 'development',
watchOptions: { watchOptions: {
// Since we use require.context, webpack is watching the entire directory. // Since we use require.context, webpack is watching the entire directory.
@ -42,7 +40,7 @@ module.exports = merge(common, {
}, },
watchFiles: ['**/*.css'], watchFiles: ['**/*.css'],
static: { static: {
directory: path.join(__dirname, '..', '/dist'), directory: fileURLToPath(new URL('../dist', import.meta.url)),
publicPath: '/dist', publicPath: '/dist',
watch: false watch: false
} }

View File

@ -1,17 +1,14 @@
/* global __dirname module */
/* /*
This configuration should be used for production installs. This configuration should be used for production installs.
It is the default webpack configuration. It is the default webpack configuration.
*/ */
const path = require('path');
const webpack = require('webpack');
const { merge } = require('webpack-merge');
const common = require('./webpack.common'); import webpack from 'webpack';
const projectRootDir = path.resolve(__dirname, '..'); import { merge } from 'webpack-merge';
module.exports = merge(common, { import common from './webpack.common.js';
export default merge(common, {
mode: 'production', mode: 'production',
plugins: [ plugins: [
new webpack.DefinePlugin({ new webpack.DefinePlugin({

View File

@ -1,6 +1,6 @@
# Open MCT License # Open MCT License
Open MCT, Copyright (c) 2014-2023, United States Government as represented by the Administrator of the National Aeronautics and Space Administration. All rights reserved. Open MCT, 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. 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.

View File

@ -85,9 +85,8 @@ There are a few reasons that your GitHub PR could be failing beyond simple faile
* Not all required checks are run per commit. You may need to manually trigger addition GitHub checks with a `pr:<label>` label added to your PR. * Not all required checks are run per commit. You may need to manually trigger addition GitHub checks with a `pr:<label>` label added to your PR.
### Flaky tests ### Flaky tests
There are two ways to know if a test on your branch is historically flaky:
1. `deploysentinel`'s PR comment bot to give an accurate and historical view of e2e flakiness. Check your PR for a view of the test failures and flakes (with link to the failing test). Note: only a 7 day window of flake is available. (CircleCI's test insights feature)[https://circleci.com/blog/introducing-test-insights-with-flaky-test-detection/] collects historical data about the individual test results for both unit and e2e tests. Note: only a 14 day window of flake is available.
2. (CircleCI's test insights feature)[https://circleci.com/blog/introducing-test-insights-with-flaky-test-detection/] collects historical data about the individual test results for both unit and e2e tests. Note: only a 14 day window of flake is available.
### Local=Pass and CI=Fail ### Local=Pass and CI=Fail
Although rare, it is possible that your test can pass locally but fail in CI. Although rare, it is possible that your test can pass locally but fail in CI.

View File

@ -1,7 +1,7 @@
#!/bin/bash #!/bin/bash
#***************************************************************************** #*****************************************************************************
#* Open MCT, Copyright (c) 2014-2023, United States Government #* Open MCT, Copyright (c) 2014-2024, United States Government
#* as represented by the Administrator of the National Aeronautics and Space #* as represented by the Administrator of the National Aeronautics and Space
#* Administration. All rights reserved. #* Administration. All rights reserved.
#* #*

View File

@ -1,5 +1,5 @@
<!-- <!--
Open MCT, Copyright (c) 2014-2023, United States Government Open MCT, Copyright (c) 2014-2024, United States Government
as represented by the Administrator of the National Aeronautics and Space as represented by the Administrator of the National Aeronautics and Space
Administration. All rights reserved. Administration. All rights reserved.

View File

@ -1,5 +1,5 @@
/***************************************************************************** /*****************************************************************************
* Open MCT, Copyright (c) 2014-2023, United States Government * Open MCT, Copyright (c) 2014-2024, United States Government
* as represented by the Administrator of the National Aeronautics and Space * as represented by the Administrator of the National Aeronautics and Space
* Administration. All rights reserved. * Administration. All rights reserved.
* *

126
docs/src/process/release.md Normal file
View File

@ -0,0 +1,126 @@
# Release of NASA Open MCT NPM Package
This document outlines the process and key considerations for releasing a new version of the NASA Open MCT project as an NPM (Node Package Manager) package.
## FAQ
1. When do we publish a new version of Open MCT?
- At the end of a working sprint (typically) after all blocking issues have been resolved.
2. Where do we publish?
- [NPM](https://www.npmjs.com/package/openmct)
- [Github Releases](https://github.com/nasa/openmct/releases)
2. What do we publish?
- What constitutes a "stable" release?
- TODO
- What constitutes a "latest" release?
- The most recently published release.
- What constitutes a "nightly" release?
- TODO
4. What necessitates a patch release?
-
## 1. Pre-requisites
Before releasing a new version of Open MCT, ensure that all dependencies are updated, and
comprehensive testing is performed.
## 2. Versioning
Open MCT follows [Semantic Versioning 2.0.0 (SemVer)](https://semver.org) that consists of three
major components: `MAJOR.MINOR.PATCH` (i.e. `1.2.3`).
Major releases are necessitated by fundamental framework changes that are expected to be incompatible
with previous releases.
Minor releases are necessitated by non-backwards-compatible application, API changes, or new
features or enhancements.
Patch releases are created for backporting fixes to blocking bugs that were discovered _after_
the release of a major or minor version. They are not to introduce new features, enhancements, or
dependency changes.
## 3. Changelog Maintenance
Changelogs can be found in the GitHub releases section of the repository and are auto-generated
using [GitHub's feature](https://docs.github.com/en/repositories/releasing-projects-on-github/automatically-generated-release-notes).
## 4. Pull Request Labeling
Generation of release notes is automated by the use of labels on pull requests. The following
labels are used to categorize pull requests:
### `type:bug`
Pull requests are to be labeled with `type:bug` if they contain changes that intend to fix a bug.
### `type:enhancement`
Pull requests are to be labeled with `type:enhancement` if they contain changes that intend to
enhance existing functionality of Open MCT.
### `type:feature`
Pull requests are to be labeled with `type:feature` if they contain changes that intend to introduce
new functionality to Open MCT.
### `type:maintenance`
Pull requests are to be labeled with `type:maintenance` if they contain changes that introduce
new tests, documentation, or other maintenance-related changes.
### `performance`
Pull requests are to be labeled with `performance` if they contain changes that are intended to
improve the performance of Open MCT.
### `notable_change`
Pull requests are to be labeled with `notable_change` if they contain changes that fit any of the
following criteria:
- **Breaking Change**
- Highlights the integration of changes that are suspected to break, or without a doubt will
break, backwards compatibility. These should signal to users the upgrade might be seamless only
if dependency and integration factors are properly managed, if not, one should expect to manage
atypical technical snags.
- **API Change**
- Signifies any change to the Open MCT API such as the addition of new methods, or the
modification or deprecation of existing methods. API changes may or may not constitute a
breaking change.
- **Default Behavior Change**
- Any change to the default behavior of Open MCT, such as the default configuration of a plugin,
or the default behavior of a user interface component or feature (i.e.: autoscale being enabled
by default on plots).
## 5. Community & Contributions
Open MCT is an open-source project and contributions are welcome. As such, it is important to
acknowledge the contributions of the community and contributors. Pull requests by contributors
will be labeled with `source:community` to signify that the contribution was made by a member of
the community.
## 6. Release Process
Currently, the release process is manual and requires the following steps:
1. Clone a fresh copy of the repository.
- `git clone git@github.com:nasa/openmct.git`
2. Check out the appropriate release branch.
- `git checkout release/1.2.3`
3. Ensure that the `package.json` file is updated with the correct version number and does not
contain the `-next` suffix (which implies a pre-release).
4. Create a tag for the release if it does not already exist.
- `git tag v1.2.3`
5. Push the tag to the repository.
- `git push origin v1.2.3`
6. Run `npm install` to install dependencies.
7. Publish the release to NPM (You will need to be logged in to an NPM account with the appropriate permissions).
- `npm publish`
8. Create a release on GitHub.
- Navigate to the Releases page on the Open MCT repository.
- Click [draft a new release.](https://github.com/nasa/openmct/releases/new)
- Choose the tag that was just created for the release.
- For "Previous tag", choose the tag that was most recently released.
- Click "Generate release notes" to auto-generate release notes.
- Click "Publish release" to publish the release.

View File

@ -26,3 +26,8 @@ snapshot:
.c-compact-tc__setting-value{ .c-compact-tc__setting-value{
opacity: 0 !important; opacity: 0 !important;
} }
/* Chart Area for Plots */
.gl-plot-chart-area{
opacity: 0 !important;
}

View File

@ -26,3 +26,7 @@ snapshot:
.c-compact-tc__setting-value{ .c-compact-tc__setting-value{
opacity: 0 !important; opacity: 0 !important;
} }
/* Chart Area for Plots */
.gl-plot-chart-area{
opacity: 0 !important;
}

View File

@ -109,7 +109,7 @@ For those interested in the mechanics of snapshot testing with Playwright, you c
// from our package.json or circleCI configuration file // from our package.json or circleCI configuration file
docker run --rm --network host -v $(pwd):/work/ -w /work/ -it mcr.microsoft.com/playwright:v{X.X.X}-focal /bin/bash docker run --rm --network host -v $(pwd):/work/ -w /work/ -it mcr.microsoft.com/playwright:v{X.X.X}-focal /bin/bash
npm install npm install
npx playwright test --config=e2e/playwright-ci.config.js --project=chrome --grep @snapshot npm run test:e2e:checksnapshots
``` ```
### Updating Snapshots ### Updating Snapshots
@ -134,6 +134,12 @@ npm install
npm run test:e2e:updatesnapshots npm run test:e2e:updatesnapshots
``` ```
Once that's done, you'll need to run the following to verify that the changes do not cause more problems:
```sh
npm run test:e2e:checksnapshots
```
## Automated Accessibility (a11y) Testing ## Automated Accessibility (a11y) Testing
Open MCT incorporates accessibility testing through two primary methods to ensure its compliance with accessibility standards: Open MCT incorporates accessibility testing through two primary methods to ensure its compliance with accessibility standards:
@ -223,7 +229,7 @@ Current list of test tags:
|Test Tag|Description| |Test Tag|Description|
|:-:|-| |:-:|-|
|`@ipad` | 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).| |`@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.| |`@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.| |`@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`.| |`@addInit` | Initializes the browser with an injected and artificial state. Useful for loading non-default plugins. Likely will not work outside of `npm start`.|
@ -323,9 +329,15 @@ In terms of operating system testing, we're only limited by what the CI provider
#### **Mobile** #### **Mobile**
We have the Mission-need to support iPad. To run our iPad suite, please see our `playwright-*.config.js` with the 'iPad' project. We have a Mission-need to support iPad and mobile devices. To run our test suites with mobile devices, please see our `playwright-mobile.config.js` projects.
In general, our test suite is not designed to run against mobile devices as the mobile experience is a focused version of the application. Core functionality is missing (chiefly the 'Create' button) and so this will likely turn into a separate suite. In general, our test suite is not designed to run against mobile devices as the mobile experience is a focused version of the application. Core functionality is missing (chiefly the 'Create' button). To bypass the object creation, we leverage the `storageState` properties for starting the mobile tests with localstorage.
For now, the mobile tests will exist in the /tests/mobile/ suites and be executed with the
```sh
npm run test:e2e:mobile
```
command.
#### **Skipping or executing tests based on browser, os, and/os browser version:** #### **Skipping or executing tests based on browser, os, and/os browser version:**
@ -480,7 +492,7 @@ The following contains a list of tips and tricks which don't exactly fit into a
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. To do this, use the `overrideClock` fixture as such:
```js ```js
const { test, expect } = require('../../pluginFixtures.js'); import { test, expect } from '../../pluginFixtures.js';
test.describe('foo test suite', () => { test.describe('foo test suite', () => {

View File

@ -1,5 +1,5 @@
/***************************************************************************** /*****************************************************************************
* Open MCT, Copyright (c) 2014-2023, United States Government * Open MCT, Copyright (c) 2014-2024, United States Government
* as represented by the Administrator of the National Aeronautics and Space * as represented by the Administrator of the National Aeronautics and Space
* Administration. All rights reserved. * Administration. All rights reserved.
* *
@ -54,9 +54,9 @@
* @property {import('../src/api/notifications/NotificationAPI').NotificationOptions} [notificationOptions] additional options * @property {import('../src/api/notifications/NotificationAPI').NotificationOptions} [notificationOptions] additional options
*/ */
const Buffer = require('buffer').Buffer; import { expect } from '@playwright/test';
const genUuid = require('uuid').v4; import { Buffer } from 'buffer';
const { expect } = require('@playwright/test'); 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 * This common function creates a domain object with the default options. It is the preferred way of creating objects
@ -284,7 +284,7 @@ async function navigateToObjectWithFixedTimeBounds(page, url, start, end) {
*/ */
async function openObjectTreeContextMenu(page, url) { async function openObjectTreeContextMenu(page, url) {
await page.goto(url); await page.goto(url);
await page.click('button[title="Show selected item in tree"]'); await page.getByLabel('Show selected item in tree').click();
await page.locator('.is-navigated-object').click({ await page.locator('.is-navigated-object').click({
button: 'right' button: 'right'
}); });
@ -644,8 +644,7 @@ async function renameObjectFromContextMenu(page, url, newName) {
await page.click('[aria-label="Save"]'); await page.click('[aria-label="Save"]');
} }
// eslint-disable-next-line no-undef export {
module.exports = {
createDomainObjectWithDefaults, createDomainObjectWithDefaults,
createExampleTelemetryObject, createExampleTelemetryObject,
createNotification, createNotification,
@ -653,16 +652,16 @@ module.exports = {
expandEntireTree, expandEntireTree,
expandTreePaneItemByName, expandTreePaneItemByName,
getCanvasPixels, getCanvasPixels,
getHashUrlToDomainObject,
getFocusedObjectUuid, getFocusedObjectUuid,
getHashUrlToDomainObject,
navigateToObjectWithFixedTimeBounds, navigateToObjectWithFixedTimeBounds,
openObjectTreeContextMenu, openObjectTreeContextMenu,
renameObjectFromContextMenu,
setEndOffset,
setFixedTimeMode, setFixedTimeMode,
setIndependentTimeConductorBounds,
setRealTimeMode, setRealTimeMode,
setStartOffset, setStartOffset,
setEndOffset,
setTimeConductorBounds, setTimeConductorBounds,
setIndependentTimeConductorBounds, waitForPlotsToRender
waitForPlotsToRender,
renameObjectFromContextMenu
}; };

View File

@ -1,5 +1,5 @@
/***************************************************************************** /*****************************************************************************
* Open MCT, Copyright (c) 2014-2023, United States Government * Open MCT, Copyright (c) 2014-2024, United States Government
* as represented by the Administrator of the National Aeronautics and Space * as represented by the Administrator of the National Aeronautics and Space
* Administration. All rights reserved. * Administration. All rights reserved.
* *
@ -33,10 +33,11 @@
* existing ones. * existing ones.
*/ */
const fs = require('fs'); import AxeBuilder from '@axe-core/playwright';
const path = require('path'); import fs from 'fs';
const { test, expect } = require('./pluginFixtures'); import path from 'path';
const AxeBuilder = require('@axe-core/playwright').default;
import { expect, test } from './pluginFixtures.js';
// Constants for repeated values // Constants for repeated values
const TEST_RESULTS_DIR = './test-results'; const TEST_RESULTS_DIR = './test-results';
@ -56,11 +57,10 @@ const TEST_RESULTS_DIR = './test-results';
* otherwise returns null. * otherwise returns null.
*/ */
/* eslint-disable no-undef */ /* eslint-disable no-undef */
exports.scanForA11yViolations = async function (page, testCaseName, options = {}) { export async function scanForA11yViolations(page, testCaseName, options = {}) {
const builder = new AxeBuilder({ page }); const builder = new AxeBuilder({ page });
builder.withTags(['wcag2aa']); builder.withTags(['wcag2aa']);
// https://github.com/dequelabs/axe-core/blob/develop/doc/rule-descriptions.md // https://github.com/dequelabs/axe-core/blob/develop/doc/rule-descriptions.md
builder.disableRules(['color-contrast']);
const accessibilityScanResults = await builder.analyze(); const accessibilityScanResults = await builder.analyze();
// Assert that no violations should be present // Assert that no violations should be present
@ -91,7 +91,6 @@ exports.scanForA11yViolations = async function (page, testCaseName, options = {}
console.log('No accessibility violations found, no report generated.'); console.log('No accessibility violations found, no report generated.');
return null; return null;
} }
}; }
exports.expect = expect; export { expect, test };
exports.test = test;

View File

@ -1,6 +1,6 @@
/* eslint-disable no-undef */ /* eslint-disable no-undef */
/***************************************************************************** /*****************************************************************************
* Open MCT, Copyright (c) 2014-2023, United States Government * Open MCT, Copyright (c) 2014-2024, United States Government
* as represented by the Administrator of the National Aeronautics and Space * as represented by the Administrator of the National Aeronautics and Space
* Administration. All rights reserved. * Administration. All rights reserved.
* *
@ -28,12 +28,12 @@
* GitHub issues. * GitHub issues.
*/ */
const base = require('@playwright/test'); import { expect, request, test } from '@playwright/test';
const { expect, request } = base; import fs from 'fs';
const fs = require('fs'); import path from 'path';
const path = require('path'); import sinon from 'sinon';
const { v4: uuid } = require('uuid'); import { fileURLToPath } from 'url';
const sinon = require('sinon'); import { v4 as uuid } from 'uuid';
/** /**
* Takes a `ConsoleMessage` and returns a formatted string. Used to enable console log error detection. * Takes a `ConsoleMessage` and returns a formatted string. Used to enable console log error detection.
@ -68,7 +68,7 @@ function waitForAnimations(locator) {
*/ */
const istanbulCLIOutput = path.join(process.cwd(), '.nyc_output'); const istanbulCLIOutput = path.join(process.cwd(), '.nyc_output');
exports.test = base.test.extend({ const extendedTest = test.extend({
/** /**
* This allows the test to manipulate the browser clock. This is useful for Visual and Snapshot tests which need * 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. * the Time Indicator Clock to be in a specific state.
@ -97,7 +97,7 @@ exports.test = base.test.extend({
async ({ context, clockOptions }, use) => { async ({ context, clockOptions }, use) => {
if (clockOptions !== undefined) { if (clockOptions !== undefined) {
await context.addInitScript({ await context.addInitScript({
path: path.join(__dirname, '../', './node_modules/sinon/pkg/sinon.js') path: fileURLToPath(new URL('../node_modules/sinon/pkg/sinon.js', import.meta.url))
}); });
await context.addInitScript((options) => { await context.addInitScript((options) => {
window.__clock = sinon.useFakeTimers(options); window.__clock = sinon.useFakeTimers(options);
@ -201,6 +201,4 @@ exports.test = base.test.extend({
} }
}); });
exports.expect = expect; export { expect, request, extendedTest as test, waitForAnimations };
exports.request = request;
exports.waitForAnimations = waitForAnimations;

View File

@ -15,4 +15,5 @@ export const MISSION_TIME = 1732413600000; // Saturday, November 23, 2024 6:00:0
* - hides the tree and inspector to prevent visual noise * - hides the tree and inspector to prevent visual noise
* - sets the time bounds to a fixed range * - sets the time bounds to a fixed range
*/ */
export const VISUAL_URL = './#/browse/mine?tc.mode=fixed&tc.startBound=1693592063607&tc.endBound=1693593893607&tc.timeSystem=utc&view=grid&hideInspector=true&hideTree=true'; export const VISUAL_URL =
'./#/browse/mine?tc.mode=fixed&tc.startBound=1693592063607&tc.endBound=1693593893607&tc.timeSystem=utc&view=grid&hideInspector=true&hideTree=true';

View File

@ -1,5 +1,5 @@
/***************************************************************************** /*****************************************************************************
* Open MCT, Copyright (c) 2014-2023, United States Government * Open MCT, Copyright (c) 2014-2024, United States Government
* as represented by the Administrator of the National Aeronautics and Space * as represented by the Administrator of the National Aeronautics and Space
* Administration. All rights reserved. * Administration. All rights reserved.
* *

View File

@ -1,5 +1,5 @@
/***************************************************************************** /*****************************************************************************
* Open MCT, Copyright (c) 2014-2023, United States Government * Open MCT, Copyright (c) 2014-2024, United States Government
* as represented by the Administrator of the National Aeronautics and Space * as represented by the Administrator of the National Aeronautics and Space
* Administration. All rights reserved. * Administration. All rights reserved.
* *

View File

@ -1,5 +1,5 @@
/***************************************************************************** /*****************************************************************************
* Open MCT, Copyright (c) 2014-2023, United States Government * Open MCT, Copyright (c) 2014-2024, United States Government
* as represented by the Administrator of the National Aeronautics and Space * as represented by the Administrator of the National Aeronautics and Space
* Administration. All rights reserved. * Administration. All rights reserved.
* *

View File

@ -1,5 +1,5 @@
/***************************************************************************** /*****************************************************************************
* Open MCT, Copyright (c) 2014-2023, United States Government * Open MCT, Copyright (c) 2014-2024, United States Government
* as represented by the Administrator of the National Aeronautics and Space * as represented by the Administrator of the National Aeronautics and Space
* Administration. All rights reserved. * Administration. All rights reserved.
* *

View File

@ -1,5 +1,5 @@
/***************************************************************************** /*****************************************************************************
* Open MCT, Copyright (c) 2014-2023, United States Government * Open MCT, Copyright (c) 2014-2024, United States Government
* as represented by the Administrator of the National Aeronautics and Space * as represented by the Administrator of the National Aeronautics and Space
* Administration. All rights reserved. * Administration. All rights reserved.
* *

View File

@ -1,5 +1,5 @@
/***************************************************************************** /*****************************************************************************
* Open MCT, Copyright (c) 2014-2023, United States Government * Open MCT, Copyright (c) 2014-2024, United States Government
* as represented by the Administrator of the National Aeronautics and Space * as represented by the Administrator of the National Aeronautics and Space
* Administration. All rights reserved. * Administration. All rights reserved.
* *

View File

@ -1,5 +1,5 @@
/***************************************************************************** /*****************************************************************************
* Open MCT, Copyright (c) 2014-2023, United States Government * Open MCT, Copyright (c) 2014-2024, United States Government
* as represented by the Administrator of the National Aeronautics and Space * as represented by the Administrator of the National Aeronautics and Space
* Administration. All rights reserved. * Administration. All rights reserved.
* *

View File

@ -1,5 +1,5 @@
/***************************************************************************** /*****************************************************************************
* Open MCT, Copyright (c) 2014-2023, United States Government * Open MCT, Copyright (c) 2014-2024, United States Government
* as represented by the Administrator of the National Aeronautics and Space * as represented by the Administrator of the National Aeronautics and Space
* Administration. All rights reserved. * Administration. All rights reserved.
* *

View File

@ -1,5 +1,5 @@
/***************************************************************************** /*****************************************************************************
* Open MCT, Copyright (c) 2014-2023, United States Government * Open MCT, Copyright (c) 2014-2024, United States Government
* as represented by the Administrator of the National Aeronautics and Space * as represented by the Administrator of the National Aeronautics and Space
* Administration. All rights reserved. * Administration. All rights reserved.
* *
@ -19,14 +19,15 @@
* this source code distribution or the Licensing information page available * this source code distribution or the Licensing information page available
* at runtime from the About dialog for additional information. * at runtime from the About dialog for additional information.
*****************************************************************************/ *****************************************************************************/
/* global __dirname */ import { fileURLToPath } from 'url';
const path = require('path');
/** /**
* @param {import('@playwright/test').Page} page * @param {import('@playwright/test').Page} page
*/ */
async function navigateToFaultManagementWithExample(page) { async function navigateToFaultManagementWithExample(page) {
await page.addInitScript({ path: path.join(__dirname, './', 'addInitExampleFaultProvider.js') }); await page.addInitScript({
path: fileURLToPath(new URL('./addInitExampleFaultProvider.js', import.meta.url))
});
await navigateToFaultItemInTree(page); await navigateToFaultItemInTree(page);
} }
@ -36,7 +37,7 @@ async function navigateToFaultManagementWithExample(page) {
*/ */
async function navigateToFaultManagementWithStaticExample(page) { async function navigateToFaultManagementWithStaticExample(page) {
await page.addInitScript({ await page.addInitScript({
path: path.join(__dirname, './', 'addInitExampleFaultProviderStatic.js') path: fileURLToPath(new URL('./addInitExampleFaultProviderStatic.js', import.meta.url))
}); });
await navigateToFaultItemInTree(page); await navigateToFaultItemInTree(page);
@ -46,7 +47,9 @@ async function navigateToFaultManagementWithStaticExample(page) {
* @param {import('@playwright/test').Page} page * @param {import('@playwright/test').Page} page
*/ */
async function navigateToFaultManagementWithoutExample(page) { async function navigateToFaultManagementWithoutExample(page) {
await page.addInitScript({ path: path.join(__dirname, './', 'addInitFaultManagementPlugin.js') }); await page.addInitScript({
path: fileURLToPath(new URL('./addInitFaultManagementPlugin.js', import.meta.url))
});
await navigateToFaultItemInTree(page); await navigateToFaultItemInTree(page);
} }
@ -265,29 +268,28 @@ async function openFaultRowMenu(page, rowNumber) {
.click(); .click();
} }
// eslint-disable-next-line no-undef export {
module.exports = {
navigateToFaultManagementWithExample,
navigateToFaultManagementWithStaticExample,
navigateToFaultManagementWithoutExample,
navigateToFaultItemInTree,
acknowledgeFault, acknowledgeFault,
shelveMultipleFaults,
acknowledgeMultipleFaults, acknowledgeMultipleFaults,
shelveFault,
changeViewTo, changeViewTo,
sortFaultsBy,
enterSearchTerm,
clearSearch, clearSearch,
selectFaultItem, enterSearchTerm,
getHighestSeverity,
getLowestSeverity,
getFaultResultCount,
getFault, getFault,
getFaultByName, getFaultByName,
getFaultName, getFaultName,
getFaultSeverity,
getFaultNamespace, getFaultNamespace,
getFaultResultCount,
getFaultSeverity,
getFaultTriggerTime, getFaultTriggerTime,
openFaultRowMenu getHighestSeverity,
getLowestSeverity,
navigateToFaultItemInTree,
navigateToFaultManagementWithExample,
navigateToFaultManagementWithoutExample,
navigateToFaultManagementWithStaticExample,
openFaultRowMenu,
selectFaultItem,
shelveFault,
shelveMultipleFaults,
sortFaultsBy
}; };

View File

@ -1,5 +1,5 @@
/***************************************************************************** /*****************************************************************************
* Open MCT, Copyright (c) 2014-2023, United States Government * Open MCT, Copyright (c) 2014-2024, United States Government
* as represented by the Administrator of the National Aeronautics and Space * as represented by the Administrator of the National Aeronautics and Space
* Administration. All rights reserved. * Administration. All rights reserved.
* *
@ -20,11 +20,11 @@
* at runtime from the About dialog for additional information. * at runtime from the About dialog for additional information.
*****************************************************************************/ *****************************************************************************/
const { createDomainObjectWithDefaults } = require('../appActions'); import { createDomainObjectWithDefaults } from '../appActions.js';
const NOTEBOOK_DROP_AREA = '.c-notebook__drag-area'; const NOTEBOOK_DROP_AREA = '.c-notebook__drag-area';
const CUSTOM_NAME = 'CUSTOM_NAME'; const CUSTOM_NAME = 'CUSTOM_NAME';
const path = require('path'); import { fileURLToPath } from 'url';
/** /**
* @param {import('@playwright/test').Page} page * @param {import('@playwright/test').Page} page
@ -49,7 +49,7 @@ async function dragAndDropEmbed(page, notebookObject) {
// Navigate to notebook // Navigate to notebook
await page.goto(notebookObject.url); await page.goto(notebookObject.url);
// Expand the tree to reveal the notebook // Expand the tree to reveal the notebook
await page.click('button[title="Show selected item in tree"]'); await page.getByLabel('Show selected item in tree').click();
// Drag and drop the SWG into the notebook // Drag and drop the SWG into the notebook
await page.dragAndDrop(`text=${swg.name}`, NOTEBOOK_DROP_AREA); await page.dragAndDrop(`text=${swg.name}`, NOTEBOOK_DROP_AREA);
await commitEntry(page); await commitEntry(page);
@ -69,7 +69,9 @@ async function commitEntry(page) {
*/ */
async function startAndAddRestrictedNotebookObject(page) { async function startAndAddRestrictedNotebookObject(page) {
// eslint-disable-next-line no-undef // eslint-disable-next-line no-undef
await page.addInitScript({ path: path.join(__dirname, 'addInitRestrictedNotebook.js') }); await page.addInitScript({
path: fileURLToPath(new URL('./addInitRestrictedNotebook.js', import.meta.url))
});
await page.goto('./', { waitUntil: 'domcontentloaded' }); await page.goto('./', { waitUntil: 'domcontentloaded' });
return createDomainObjectWithDefaults(page, { return createDomainObjectWithDefaults(page, {
@ -138,12 +140,11 @@ async function createNotebookEntryAndTags(page, iterations = 1) {
return notebook; return notebook;
} }
// eslint-disable-next-line no-undef export {
module.exports = { createNotebookAndEntry,
enterTextEntry,
dragAndDropEmbed,
startAndAddRestrictedNotebookObject,
lockPage,
createNotebookEntryAndTags, createNotebookEntryAndTags,
createNotebookAndEntry dragAndDropEmbed,
enterTextEntry,
lockPage,
startAndAddRestrictedNotebookObject
}; };

View File

@ -1,5 +1,5 @@
/***************************************************************************** /*****************************************************************************
* Open MCT, Copyright (c) 2014-2023, United States Government * Open MCT, Copyright (c) 2014-2024, United States Government
* as represented by the Administrator of the National Aeronautics and Space * as represented by the Administrator of the National Aeronautics and Space
* Administration. All rights reserved. * Administration. All rights reserved.
* *
@ -20,7 +20,7 @@
* at runtime from the About dialog for additional information. * at runtime from the About dialog for additional information.
*****************************************************************************/ *****************************************************************************/
import { expect } from '../pluginFixtures'; import { expect } from '../pluginFixtures.js';
/** /**
* Asserts that the number of activities in the plan view matches the number of * Asserts that the number of activities in the plan view matches the number of
@ -113,17 +113,35 @@ export async function assertPlanOrderedSwimLanes(page, plan, objectUrl) {
* @param {string} planObjectUrl * @param {string} planObjectUrl
*/ */
export async function setBoundsToSpanAllActivities(page, planJson, planObjectUrl) { export async function setBoundsToSpanAllActivities(page, planJson, planObjectUrl) {
const activities = Object.values(planJson).flat();
// Get the earliest start value // Get the earliest start value
const start = Math.min(...activities.map((activity) => activity.start)); const start = getEarliestStartTime(planJson);
// Get the latest end value // Get the latest end value
const end = Math.max(...activities.map((activity) => activity.end)); const end = getLatestEndTime(planJson);
// Set the start and end bounds to the earliest start and latest end // Set the start and end bounds to the earliest start and latest end
await page.goto( await page.goto(
`${planObjectUrl}?tc.mode=fixed&tc.startBound=${start}&tc.endBound=${end}&tc.timeSystem=utc&view=plan.view` `${planObjectUrl}?tc.mode=fixed&tc.startBound=${start}&tc.endBound=${end}&tc.timeSystem=utc&view=plan.view`
); );
} }
/**
* @param {object} planJson
* @returns {number}
*/
export function getEarliestStartTime(planJson) {
const activities = Object.values(planJson).flat();
return Math.min(...activities.map((activity) => activity.start));
}
/**
*
* @param {object} planJson
* @returns {number}
*/
export function getLatestEndTime(planJson) {
const activities = Object.values(planJson).flat();
return Math.max(...activities.map((activity) => activity.end));
}
/** /**
* Uses the Open MCT API to set the status of a plan to 'draft'. * Uses the Open MCT API to set the status of a plan to 'draft'.
* @param {import('@playwright/test').Page} page * @param {import('@playwright/test').Page} page

View File

@ -1,5 +1,5 @@
/***************************************************************************** /*****************************************************************************
* Open MCT, Copyright (c) 2014-2023, United States Government * Open MCT, Copyright (c) 2014-2024, United States Government
* as represented by the Administrator of the National Aeronautics and Space * as represented by the Administrator of the National Aeronautics and Space
* Administration. All rights reserved. * Administration. All rights reserved.
* *
@ -20,8 +20,8 @@
* at runtime from the About dialog for additional information. * at runtime from the About dialog for additional information.
*****************************************************************************/ *****************************************************************************/
import { expect } from '../pluginFixtures'; import { waitForPlotsToRender } from '../appActions.js';
const { waitForPlotsToRender } = require('../appActions'); import { expect } from '../pluginFixtures.js';
/** /**
* Given a canvas and a set of points, tags the points on the canvas. * Given a canvas and a set of points, tags the points on the canvas.

104
e2e/helper/stylingUtils.js Normal file
View File

@ -0,0 +1,104 @@
/*****************************************************************************
* Open MCT, Copyright (c) 2014-2024, United States Government
* as represented by the Administrator of the National Aeronautics and Space
* Administration. All rights reserved.
*
* Open MCT is licensed under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0.
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*
* Open MCT includes source code licensed under additional open source
* licenses. See the Open Source Licenses file (LICENSES.md) included with
* this source code distribution or the Licensing information page available
* at runtime from the About dialog for additional information.
*****************************************************************************/
import { expect } from '../pluginFixtures.js';
/**
* Converts a hex color value to its RGB equivalent.
*
* @param {string} hex - The hex color value. i.e. '#5b0f00'
* @returns {string} The RGB equivalent of the hex color.
*/
function hexToRGB(hex) {
const result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex);
return result
? `rgb(${parseInt(result[1], 16)}, ${parseInt(result[2], 16)}, ${parseInt(result[3], 16)})`
: null;
}
/**
* Sets the background and text color of a given element.
*
* @param {import('@playwright/test').Page} page - The Playwright page object.
* @param {string} borderColorHex - The hex value of the border color to set, or 'No Style'.
* @param {string} backgroundColorHex - The hex value of the background color to set, or 'No Style'.
* @param {string} textColorHex - The hex value of the text color to set, or 'No Style'.
* @param {import('@playwright/test').Locator} locator - The Playwright locator for the element whose style is to be set.
*/
async function setStyles(page, borderColorHex, backgroundColorHex, textColorHex, locator) {
await locator.click(); // Assuming the locator is clickable and opens the style setting UI
await page.getByLabel('Set border color').click();
await page.getByLabel(borderColorHex).click();
await page.getByLabel('Set background color').click();
await page.getByLabel(backgroundColorHex).click();
await page.getByLabel('Set text color').click();
await page.getByLabel(textColorHex).click();
}
/**
* Checks if the styles of an element match the expected values.
*
* @param {string} expectedBorderColor - The expected border color in RGB format. Default is '#e6b8af' or 'rgb(230, 184, 175)'
* @param {string} expectedBackgroundColor - The expected background color in RGB format.
* @param {string} expectedTextColor - The expected text color in RGB format. Default is #aaaaaa or 'rgb(170, 170, 170)'
* @param {import('@playwright/test').Locator} locator - The Playwright locator for the element whose style is to be checked.
*/
async function checkStyles(
expectedBorderColor,
expectedBackgroundColor,
expectedTextColor,
locator
) {
const layoutStyles = await locator.evaluate((el) => {
return {
border: window.getComputedStyle(el).getPropertyValue('border-top-color'), //infer the left, right, and bottom
background: window.getComputedStyle(el).getPropertyValue('background-color'),
fontColor: window.getComputedStyle(el).getPropertyValue('color')
};
});
expect(layoutStyles.border).toContain(expectedBorderColor);
expect(layoutStyles.background).toContain(expectedBackgroundColor);
expect(layoutStyles.fontColor).toContain(expectedTextColor);
}
/**
* Checks if the font Styles of an element match the expected values.
*
* @param {string} expectedFontSize - The expected font size in '72px' format. Default is 'Default'
* @param {string} expectedFontWeight - The expected font Type. Format as '700' for bold. Default is 'Default'
* @param {string} expectedFontFamily - The expected font Type. Format as "\"Andale Mono\", sans-serif". Default is 'Default'
* @param {import('@playwright/test').Locator} locator - The Playwright locator for the element whose style is to be checked.
*/
async function checkFontStyles(expectedFontSize, expectedFontWeight, expectedFontFamily, locator) {
const layoutStyles = await locator.evaluate((el) => {
return {
fontSize: window.getComputedStyle(el).getPropertyValue('font-size'),
fontWeight: window.getComputedStyle(el).getPropertyValue('font-weight'),
fontFamily: window.getComputedStyle(el).getPropertyValue('font-family')
};
});
expect(layoutStyles.fontSize).toContain(expectedFontSize);
expect(layoutStyles.fontWeight).toContain(expectedFontWeight);
expect(layoutStyles.fontFamily).toContain(expectedFontFamily);
}
export { checkFontStyles, checkStyles, hexToRGB, setStyles };

View File

@ -1,5 +1,5 @@
/***************************************************************************** /*****************************************************************************
* Open MCT, Copyright (c) 2014-2023, United States Government * Open MCT, Copyright (c) 2014-2024, United States Government
* as represented by the Administrator of the National Aeronautics and Space * as represented by the Administrator of the National Aeronautics and Space
* Administration. All rights reserved. * Administration. All rights reserved.
* *

View File

@ -1,9 +1,8 @@
/* eslint-disable no-undef */
// playwright.config.js // playwright.config.js
// @ts-check // @ts-check
// eslint-disable-next-line no-unused-vars // eslint-disable-next-line no-unused-vars
const { devices } = require('@playwright/test'); import { devices } from '@playwright/test';
const MAX_FAILURES = 5; const MAX_FAILURES = 5;
const NUM_WORKERS = 2; const NUM_WORKERS = 2;
@ -11,6 +10,7 @@ const NUM_WORKERS = 2;
const config = { const config = {
retries: 2, //Retries 2 times for a total of 3 runs. When running sharded and with max-failures=5, this should ensure that flake is managed without failing the full suite retries: 2, //Retries 2 times for a total of 3 runs. When running sharded and with max-failures=5, this should ensure that flake is managed without failing the full suite
testDir: 'tests', testDir: 'tests',
grepInvert: /@mobile/, //Ignore mobile tests
testIgnore: '**/*.perf.spec.js', //Ignore performance tests and define in playwright-perfromance.config.js testIgnore: '**/*.perf.spec.js', //Ignore performance tests and define in playwright-perfromance.config.js
timeout: 60 * 1000, timeout: 60 * 1000,
webServer: { webServer: {
@ -76,9 +76,8 @@ const config = {
outputFolder: '../html-test-results' //Must be in different location due to https://github.com/microsoft/playwright/issues/12840 outputFolder: '../html-test-results' //Must be in different location due to https://github.com/microsoft/playwright/issues/12840
} }
], ],
['junit', { outputFile: '../test-results/results.xml' }], ['junit', { outputFile: '../test-results/results.xml' }]
['@deploysentinel/playwright']
] ]
}; };
module.exports = config; export default config;

View File

@ -1,14 +1,11 @@
/* eslint-disable no-undef */
// playwright.config.js // playwright.config.js
// @ts-check // @ts-check
// eslint-disable-next-line no-unused-vars
const { devices } = require('@playwright/test');
/** @type {import('@playwright/test').PlaywrightTestConfig} */ /** @type {import('@playwright/test').PlaywrightTestConfig} */
const config = { const config = {
retries: 0, retries: 0,
testDir: 'tests', testDir: 'tests',
testMatch: '**/*.e2e.spec.js', // only run e2e tests
testIgnore: '**/*.perf.spec.js', testIgnore: '**/*.perf.spec.js',
timeout: 30 * 1000, timeout: 30 * 1000,
webServer: { webServer: {
@ -36,7 +33,6 @@ const config = {
}, },
{ {
name: 'MMOC', name: 'MMOC',
testMatch: '**/*.e2e.spec.js', // only run e2e tests
grepInvert: /@snapshot/, grepInvert: /@snapshot/,
use: { use: {
browserName: 'chromium', browserName: 'chromium',
@ -48,8 +44,6 @@ const config = {
}, },
{ {
name: 'safari', name: 'safari',
testMatch: '**/*.e2e.spec.js', // only run e2e tests
grep: /@ipad/, // only run ipad tests due to this bug https://github.com/microsoft/playwright/issues/8340
grepInvert: /@snapshot/, grepInvert: /@snapshot/,
use: { use: {
browserName: 'webkit' browserName: 'webkit'
@ -57,7 +51,6 @@ const config = {
}, },
{ {
name: 'firefox', name: 'firefox',
testMatch: '**/*.e2e.spec.js', // only run e2e tests
grepInvert: /@snapshot/, grepInvert: /@snapshot/,
use: { use: {
browserName: 'firefox' browserName: 'firefox'
@ -65,7 +58,6 @@ const config = {
}, },
{ {
name: 'canary', name: 'canary',
testMatch: '**/*.e2e.spec.js', // only run e2e tests
grepInvert: /@snapshot/, grepInvert: /@snapshot/,
use: { use: {
browserName: 'chromium', browserName: 'chromium',
@ -74,22 +66,11 @@ const config = {
}, },
{ {
name: 'chrome-beta', name: 'chrome-beta',
testMatch: '**/*.e2e.spec.js', // only run e2e tests
grepInvert: /@snapshot/, grepInvert: /@snapshot/,
use: { use: {
browserName: 'chromium', browserName: 'chromium',
channel: 'chrome-beta' channel: 'chrome-beta'
} }
},
{
name: 'ipad',
testMatch: '**/*.e2e.spec.js', // only run e2e tests
grep: /@ipad/,
grepInvert: /@snapshot/,
use: {
browserName: 'webkit',
...devices['iPad (gen 7) landscape'] // Complete List https://github.com/microsoft/playwright/blob/main/packages/playwright-core/src/server/deviceDescriptorsSource.json
}
} }
], ],
reporter: [ reporter: [
@ -104,4 +85,4 @@ const config = {
] ]
}; };
module.exports = config; export default config;

View File

@ -0,0 +1,69 @@
// playwright.config.js
// @ts-check
import { devices } from '@playwright/test';
const MAX_FAILURES = 5;
const NUM_WORKERS = 2;
import { fileURLToPath } from 'url';
/** @type {import('@playwright/test').PlaywrightTestConfig} */
const config = {
retries: 1, //Retries 2 times for a total of 3 runs. When running sharded and with max-failures=5, this should ensure that flake is managed without failing the full suite
testDir: 'tests',
testIgnore: '**/*.perf.spec.js', //Ignore performance tests and define in playwright-perfromance.config.js
timeout: 30 * 1000,
webServer: {
command: 'npm run start:coverage',
url: 'http://localhost:8080/#',
timeout: 200 * 1000,
reuseExistingServer: true //This was originally disabled to prevent differences in local debugging vs. CI. However, it significantly speeds up local debugging.
},
maxFailures: MAX_FAILURES, //Limits failures to 5 to reduce CI Waste
workers: NUM_WORKERS, //Limit to 2 for CircleCI Agent
use: {
baseURL: 'http://localhost:8080/',
headless: true,
ignoreHTTPSErrors: true,
screenshot: 'only-on-failure',
trace: 'on-first-retry',
video: 'off'
},
projects: [
{
name: 'ipad',
grep: /@mobile/,
use: {
storageState: fileURLToPath(
new URL('./test-data/display_layout_with_child_layouts.json', import.meta.url)
),
browserName: 'webkit',
...devices['iPad (gen 7) landscape'] // Complete List https://github.com/microsoft/playwright/blob/main/packages/playwright-core/src/server/deviceDescriptorsSource.json
}
},
{
name: 'iphone',
grep: /@mobile/,
use: {
storageState: fileURLToPath(
new URL('./test-data/display_layout_with_child_layouts.json', import.meta.url)
),
browserName: 'webkit',
...devices['iPhone 14 Pro'] // Complete List https://github.com/microsoft/playwright/blob/main/packages/playwright-core/src/server/deviceDescriptorsSource.json
}
}
],
reporter: [
['list'],
[
'html',
{
open: 'never',
outputFolder: '../html-test-results' //Must be in different location due to https://github.com/microsoft/playwright/issues/12840
}
],
['junit', { outputFile: '../test-results/results.xml' }]
]
};
export default config;

View File

@ -1,4 +1,3 @@
/* eslint-disable no-undef */
// playwright.config.js // playwright.config.js
// @ts-check // @ts-check
@ -40,4 +39,4 @@ const config = {
] ]
}; };
module.exports = config; export default config;

View File

@ -1,4 +1,3 @@
/* eslint-disable no-undef */
// playwright.config.js // playwright.config.js
// @ts-check // @ts-check
@ -57,4 +56,4 @@ const config = {
] ]
}; };
module.exports = config; export default config;

View File

@ -51,4 +51,4 @@ const config = {
] ]
}; };
module.exports = config; export default config;

View File

@ -1,15 +1,12 @@
/* eslint-disable no-undef */
// playwright.config.js // playwright.config.js
// @ts-check // @ts-check
// eslint-disable-next-line no-unused-vars import { devices } from '@playwright/test';
const { devices } = require('@playwright/test'); import { fileURLToPath } from 'url';
const MAX_FAILURES = 5;
const NUM_WORKERS = 2;
/** @type {import('@playwright/test').PlaywrightTestConfig} */ /** @type {import('@playwright/test').PlaywrightTestConfig} */
const config = { const config = {
retries: 0, //Retries 2 times for a total of 3 runs. When running sharded and with max-failures=5, this should ensure that flake is managed without failing the full suite retries: 0, //Retries are not needed with watch mode
testDir: 'tests', testDir: 'tests',
timeout: 60 * 1000, timeout: 60 * 1000,
webServer: { webServer: {
@ -18,8 +15,7 @@ const config = {
timeout: 200 * 1000, timeout: 200 * 1000,
reuseExistingServer: true //This was originally disabled to prevent differences in local debugging vs. CI. However, it significantly speeds up local debugging. reuseExistingServer: true //This was originally disabled to prevent differences in local debugging vs. CI. However, it significantly speeds up local debugging.
}, },
maxFailures: MAX_FAILURES, //Limits failures to 5 to reduce CI Waste workers: '75%', //Limit to 75% of the CPU to support running with dev server
workers: NUM_WORKERS, //Limit to 2 for CircleCI Agent
use: { use: {
baseURL: 'http://localhost:8080/', baseURL: 'http://localhost:8080/',
headless: true, headless: true,
@ -35,6 +31,28 @@ const config = {
use: { use: {
browserName: 'chromium' browserName: 'chromium'
} }
},
{
name: 'ipad',
grep: /@mobile/,
use: {
storageState: fileURLToPath(
new URL('./test-data/display_layout_with_child_layouts.json', import.meta.url)
),
browserName: 'webkit',
...devices['iPad (gen 7) landscape'] // Complete List https://github.com/microsoft/playwright/blob/main/packages/playwright-core/src/server/deviceDescriptorsSource.json
}
},
{
name: 'iphone',
grep: /@mobile/,
use: {
storageState: fileURLToPath(
new URL('./test-data/display_layout_with_child_layouts.json', import.meta.url)
),
browserName: 'webkit',
...devices['iPhone 14 Pro'] // Complete List https://github.com/microsoft/playwright/blob/main/packages/playwright-core/src/server/deviceDescriptorsSource.json
}
} }
], ],
reporter: [ reporter: [
@ -46,9 +64,8 @@ const config = {
outputFolder: '../html-test-results' //Must be in different location due to https://github.com/microsoft/playwright/issues/12840 outputFolder: '../html-test-results' //Must be in different location due to https://github.com/microsoft/playwright/issues/12840
} }
], ],
['junit', { outputFile: '../test-results/results.xml' }], ['junit', { outputFile: '../test-results/results.xml' }]
['@deploysentinel/playwright']
] ]
}; };
module.exports = config; export default config;

View File

@ -1,6 +1,6 @@
/* eslint-disable no-undef */ /* eslint-disable no-undef */
/***************************************************************************** /*****************************************************************************
* Open MCT, Copyright (c) 2014-2023, United States Government * Open MCT, Copyright (c) 2014-2024, United States Government
* as represented by the Administrator of the National Aeronautics and Space * as represented by the Administrator of the National Aeronautics and Space
* Administration. All rights reserved. * Administration. All rights reserved.
* *
@ -26,9 +26,10 @@
* and appActions. These fixtures should be generalized across all plugins. * and appActions. These fixtures should be generalized across all plugins.
*/ */
const { test, expect, request } = require('./baseFixtures'); // import { createDomainObjectWithDefaults } from './appActions.js';
// const { createDomainObjectWithDefaults } = require('./appActions'); import { fileURLToPath } from 'url';
const path = require('path');
import { expect, request, test } from './baseFixtures.js';
/** /**
* @typedef {Object} ObjectCreateOptions * @typedef {Object} ObjectCreateOptions
@ -117,7 +118,7 @@ const theme = 'espresso';
*/ */
const myItemsFolderName = 'My Items'; const myItemsFolderName = 'My Items';
exports.test = test.extend({ const extendedTest = test.extend({
// This should follow in the Project's configuration. Can be set to 'snow' in playwright config.js // This should follow in the Project's configuration. Can be set to 'snow' in playwright config.js
theme: [theme, { option: true }], theme: [theme, { option: true }],
// eslint-disable-next-line no-shadow // eslint-disable-next-line no-shadow
@ -125,7 +126,9 @@ exports.test = test.extend({
// eslint-disable-next-line playwright/no-conditional-in-test // eslint-disable-next-line playwright/no-conditional-in-test
if (theme === 'snow') { if (theme === 'snow') {
//inject snow theme //inject snow theme
await page.addInitScript({ path: path.join(__dirname, './helper', './useSnowTheme.js') }); await page.addInitScript({
path: fileURLToPath(new URL('./helper/useSnowTheme.js', import.meta.url))
});
} }
// Attach info about the currently running test and its project. // Attach info about the currently running test and its project.
@ -142,19 +145,18 @@ exports.test = test.extend({
} }
}); });
exports.expect = expect; export { expect, request, extendedTest as test };
exports.request = request;
/** /**
* Takes a readable stream and returns a string. * Takes a readable stream and returns a string.
* @param {ReadableStream} readable - the readable stream * @param {ReadableStream} readable - the readable stream
* @return {Promise<String>} the stringified stream * @return {Promise<String>} the stringified stream
*/ */
exports.streamToString = async function (readable) { export async function streamToString(readable) {
let result = ''; let result = '';
for await (const chunk of readable) { for await (const chunk of readable) {
result += chunk; result += chunk;
} }
return result; return result;
}; }

File diff suppressed because one or more lines are too long

View File

@ -6,7 +6,8 @@
"end": 1660343797000, "end": 1660343797000,
"type": "Group 1", "type": "Group 1",
"color": "orange", "color": "orange",
"textColor": "white" "textColor": "white",
"id": 1
}, },
{ {
"name": "Past event 2", "name": "Past event 2",
@ -14,7 +15,8 @@
"end": 1660429160000, "end": 1660429160000,
"type": "Group 1", "type": "Group 1",
"color": "orange", "color": "orange",
"textColor": "white" "textColor": "white",
"id": 2
}, },
{ {
"name": "Past event 3", "name": "Past event 3",
@ -22,7 +24,8 @@
"end": 1660503981000, "end": 1660503981000,
"type": "Group 1", "type": "Group 1",
"color": "orange", "color": "orange",
"textColor": "white" "textColor": "white",
"id": 3
}, },
{ {
"name": "Past event 4", "name": "Past event 4",
@ -30,7 +33,8 @@
"end": 1660624108000, "end": 1660624108000,
"type": "Group 1", "type": "Group 1",
"color": "orange", "color": "orange",
"textColor": "white" "textColor": "white",
"id": 4
}, },
{ {
"name": "Past event 5", "name": "Past event 5",
@ -38,7 +42,8 @@
"end": 1660681529000, "end": 1660681529000,
"type": "Group 1", "type": "Group 1",
"color": "orange", "color": "orange",
"textColor": "white" "textColor": "white",
"id": 5
} }
] ]
} }

View File

@ -0,0 +1,42 @@
{
"Group 1": [
{
"name": "Time until birthday",
"start": 1650320402000,
"end": 1660343797000,
"type": "Group 1",
"color": "orange",
"textColor": "white",
"id": 1
},
{
"name": "Time until supper",
"start": 1650320402000,
"end": 1650420410000,
"type": "Group 2",
"color": "blue",
"textColor": "white",
"id": 2
}
],
"Group 2": [
{
"name": "Time since the last time I ate",
"start": 1650320102001,
"end": 1650320102001,
"type": "Group 2",
"color": "green",
"textColor": "white",
"id": 3
},
{
"name": "Time since last accident",
"start": 1650320102002,
"end": 1650320102002,
"type": "Group 1",
"color": "yellow",
"textColor": "white",
"id": 4
}
]
}

File diff suppressed because one or more lines are too long

View File

@ -6,11 +6,11 @@
"localStorage": [ "localStorage": [
{ {
"name": "mct", "name": "mct",
"value": "{\"mine\":{\"identifier\":{\"key\":\"mine\",\"namespace\":\"\"},\"name\":\"My Items\",\"type\":\"folder\",\"composition\":[{\"key\":\"20e7d5fe-9cf8-4099-8957-9453a8954c67\",\"namespace\":\"\"},{\"key\":\"2db521a9-996d-4d04-a171-93f4c5c220af\",\"namespace\":\"\"}],\"location\":\"ROOT\",\"modified\":1732413602540,\"created\":1732413600760,\"persisted\":1732413602540},\"20e7d5fe-9cf8-4099-8957-9453a8954c67\":{\"identifier\":{\"key\":\"20e7d5fe-9cf8-4099-8957-9453a8954c67\",\"namespace\":\"\"},\"name\":\"Overlay Plot with Telemetry Object\",\"type\":\"telemetry.plot.overlay\",\"composition\":[{\"key\":\"2db521a9-996d-4d04-a171-93f4c5c220af\",\"namespace\":\"\"}],\"configuration\":{\"series\":[]},\"notes\":\"framework/generateLocalStorageData.e2e.spec.js\\nGenerate Visual Test Data @localStorage @generatedata\\nGenerate Overlay Plot with Telemetry Object\\nchrome\",\"modified\":1732413603960,\"location\":\"mine\",\"created\":1732413601820,\"persisted\":1732413603960},\"2db521a9-996d-4d04-a171-93f4c5c220af\":{\"name\":\"VIPER Rover Heading\",\"type\":\"generator\",\"identifier\":{\"key\":\"2db521a9-996d-4d04-a171-93f4c5c220af\",\"namespace\":\"\"},\"telemetry\":{\"period\":10,\"amplitude\":1,\"offset\":0,\"dataRateInHz\":1,\"phase\":0,\"randomness\":0,\"loadDelay\":0,\"infinityValues\":false,\"staleness\":false},\"modified\":1732413602540,\"location\":\"mine\",\"created\":1732413602540,\"persisted\":1732413602540}}" "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", "name": "mct-recent-objects",
"value": "[{\"objectPath\":[{\"identifier\":{\"key\":\"2db521a9-996d-4d04-a171-93f4c5c220af\",\"namespace\":\"\"},\"name\":\"VIPER Rover Heading\",\"type\":\"generator\",\"telemetry\":{\"period\":10,\"amplitude\":1,\"offset\":0,\"dataRateInHz\":1,\"phase\":0,\"randomness\":0,\"loadDelay\":0,\"infinityValues\":false,\"staleness\":false},\"modified\":1732413602540,\"location\":\"mine\",\"created\":1732413602540,\"persisted\":1732413602540},{\"identifier\":{\"key\":\"mine\",\"namespace\":\"\"},\"name\":\"My Items\",\"type\":\"folder\",\"composition\":[{\"key\":\"20e7d5fe-9cf8-4099-8957-9453a8954c67\",\"namespace\":\"\"},{\"key\":\"2db521a9-996d-4d04-a171-93f4c5c220af\",\"namespace\":\"\"}],\"location\":\"ROOT\",\"modified\":1732413602540,\"created\":1732413600760,\"persisted\":1732413602540},{\"identifier\":{\"key\":\"ROOT\",\"namespace\":\"\"},\"name\":\"Open MCT\",\"type\":\"root\",\"composition\":[{\"key\":\"mine\",\"namespace\":\"\"}]}],\"navigationPath\":\"/browse/mine/2db521a9-996d-4d04-a171-93f4c5c220af\",\"domainObject\":{\"identifier\":{\"key\":\"2db521a9-996d-4d04-a171-93f4c5c220af\",\"namespace\":\"\"},\"name\":\"VIPER Rover Heading\",\"type\":\"generator\",\"telemetry\":{\"period\":10,\"amplitude\":1,\"offset\":0,\"dataRateInHz\":1,\"phase\":0,\"randomness\":0,\"loadDelay\":0,\"infinityValues\":false,\"staleness\":false},\"modified\":1732413602540,\"location\":\"mine\",\"created\":1732413602540,\"persisted\":1732413602540}},{\"objectPath\":[{\"identifier\":{\"key\":\"20e7d5fe-9cf8-4099-8957-9453a8954c67\",\"namespace\":\"\"},\"name\":\"Overlay Plot with Telemetry Object\",\"type\":\"telemetry.plot.overlay\",\"composition\":[{\"key\":\"2db521a9-996d-4d04-a171-93f4c5c220af\",\"namespace\":\"\"}],\"configuration\":{\"series\":[{\"identifier\":{\"key\":\"2db521a9-996d-4d04-a171-93f4c5c220af\",\"namespace\":\"\"}}]},\"notes\":\"framework/generateLocalStorageData.e2e.spec.js\\nGenerate Visual Test Data @localStorage @generatedata\\nGenerate Overlay Plot with Telemetry Object\\nchrome\",\"modified\":1732413603960,\"location\":\"mine\",\"created\":1732413601820,\"persisted\":1732413603960},{\"identifier\":{\"key\":\"mine\",\"namespace\":\"\"},\"name\":\"My Items\",\"type\":\"folder\",\"composition\":[{\"key\":\"20e7d5fe-9cf8-4099-8957-9453a8954c67\",\"namespace\":\"\"},{\"key\":\"2db521a9-996d-4d04-a171-93f4c5c220af\",\"namespace\":\"\"}],\"location\":\"ROOT\",\"modified\":1732413602540,\"created\":1732413600760,\"persisted\":1732413602540},{\"identifier\":{\"key\":\"ROOT\",\"namespace\":\"\"},\"name\":\"Open MCT\",\"type\":\"root\",\"composition\":[{\"key\":\"mine\",\"namespace\":\"\"}]}],\"navigationPath\":\"/browse/mine/20e7d5fe-9cf8-4099-8957-9453a8954c67\",\"domainObject\":{\"identifier\":{\"key\":\"20e7d5fe-9cf8-4099-8957-9453a8954c67\",\"namespace\":\"\"},\"name\":\"Overlay Plot with Telemetry Object\",\"type\":\"telemetry.plot.overlay\",\"composition\":[{\"key\":\"2db521a9-996d-4d04-a171-93f4c5c220af\",\"namespace\":\"\"}],\"configuration\":{\"series\":[{\"identifier\":{\"key\":\"2db521a9-996d-4d04-a171-93f4c5c220af\",\"namespace\":\"\"}}]},\"notes\":\"framework/generateLocalStorageData.e2e.spec.js\\nGenerate Visual Test Data @localStorage @generatedata\\nGenerate Overlay Plot with Telemetry Object\\nchrome\",\"modified\":1732413603960,\"location\":\"mine\",\"created\":1732413601820,\"persisted\":1732413603960}},{\"objectPath\":[{\"identifier\":{\"key\":\"mine\",\"namespace\":\"\"},\"name\":\"My Items\",\"type\":\"folder\",\"composition\":[{\"key\":\"20e7d5fe-9cf8-4099-8957-9453a8954c67\",\"namespace\":\"\"},{\"key\":\"2db521a9-996d-4d04-a171-93f4c5c220af\",\"namespace\":\"\"}],\"location\":\"ROOT\",\"modified\":1732413602540,\"created\":1732413600760,\"persisted\":1732413602540},{\"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\":\"20e7d5fe-9cf8-4099-8957-9453a8954c67\",\"namespace\":\"\"},{\"key\":\"2db521a9-996d-4d04-a171-93f4c5c220af\",\"namespace\":\"\"}],\"location\":\"ROOT\",\"modified\":1732413602540,\"created\":1732413600760,\"persisted\":1732413602540}}]" "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}}]"
}, },
{ {
"name": "mct-tree-expanded", "name": "mct-tree-expanded",

View File

@ -6,7 +6,7 @@
"localStorage": [ "localStorage": [
{ {
"name": "mct", "name": "mct",
"value": "{\"mine\":{\"identifier\":{\"key\":\"mine\",\"namespace\":\"\"},\"name\":\"My Items\",\"type\":\"folder\",\"composition\":[{\"key\":\"98161570-a735-4a50-9c75-11b346ad3789\",\"namespace\":\"\"}],\"location\":\"ROOT\",\"modified\":1732413601340,\"created\":1732413600580,\"persisted\":1732413601340},\"98161570-a735-4a50-9c75-11b346ad3789\":{\"identifier\":{\"key\":\"98161570-a735-4a50-9c75-11b346ad3789\",\"namespace\":\"\"},\"name\":\"Overlay Plot with 5s Delay\",\"type\":\"telemetry.plot.overlay\",\"composition\":[{\"key\":\"477e60bb-4cba-4603-b4c9-2281ccf7e054\",\"namespace\":\"\"}],\"configuration\":{\"series\":[{\"identifier\":{\"key\":\"477e60bb-4cba-4603-b4c9-2281ccf7e054\",\"namespace\":\"\"}}]},\"notes\":\"framework/generateLocalStorageData.e2e.spec.js\\nGenerate Visual Test Data @localStorage @generatedata\\nGenerate Overlay Plot with 5s Delay\\nchrome\",\"modified\":1732413602660,\"location\":\"mine\",\"created\":1732413601340,\"persisted\":1732413602660},\"477e60bb-4cba-4603-b4c9-2281ccf7e054\":{\"identifier\":{\"key\":\"477e60bb-4cba-4603-b4c9-2281ccf7e054\",\"namespace\":\"\"},\"name\":\"VIPER Rover Heading\",\"type\":\"generator\",\"telemetry\":{\"period\":10,\"amplitude\":1,\"offset\":0,\"dataRateInHz\":1,\"phase\":0,\"randomness\":0,\"loadDelay\":5000,\"infinityValues\":false,\"staleness\":false},\"modified\":1732413602520,\"location\":\"98161570-a735-4a50-9c75-11b346ad3789\",\"created\":1732413602040,\"persisted\":1732413602520}}" "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}}"
}, },
{ {
"name": "mct-tree-expanded", "name": "mct-tree-expanded",

File diff suppressed because one or more lines are too long

View File

@ -1,5 +1,5 @@
/***************************************************************************** /*****************************************************************************
* Open MCT, Copyright (c) 2014-2023, United States Government * Open MCT, Copyright (c) 2014-2024, United States Government
* as represented by the Administrator of the National Aeronautics and Space * as represented by the Administrator of the National Aeronautics and Space
* Administration. All rights reserved. * Administration. All rights reserved.
* *
@ -20,12 +20,13 @@
* at runtime from the About dialog for additional information. * at runtime from the About dialog for additional information.
*****************************************************************************/ *****************************************************************************/
const { test, expect } = require('../../pluginFixtures.js'); import {
const {
createDomainObjectWithDefaults, createDomainObjectWithDefaults,
createNotification, createNotification,
expandEntireTree expandEntireTree,
} = require('../../appActions.js'); openObjectTreeContextMenu
} from '../../appActions.js';
import { expect, test } from '../../pluginFixtures.js';
test.describe('AppActions', () => { test.describe('AppActions', () => {
test('createDomainObjectsWithDefaults', async ({ page }) => { test('createDomainObjectsWithDefaults', async ({ page }) => {
@ -155,7 +156,7 @@ test.describe('AppActions', () => {
await page.goto('./#/browse/mine'); await page.goto('./#/browse/mine');
//Click the Create button //Click the Create button
await page.click('button:has-text("Create")'); await page.getByRole('button', { name: 'Create' }).click();
// Click the object specified by 'type' // Click the object specified by 'type'
await page.click(`li[role='menuitem']:text("Clock")`); await page.click(`li[role='menuitem']:text("Clock")`);
@ -166,4 +167,13 @@ test.describe('AppActions', () => {
const locatorTreeCollapsedItems = locatorTree.locator('role=treeitem[expanded=false]'); const locatorTreeCollapsedItems = locatorTree.locator('role=treeitem[expanded=false]');
expect(await locatorTreeCollapsedItems.count()).toBe(0); expect(await locatorTreeCollapsedItems.count()).toBe(0);
}); });
test('openObjectTreeContextMenu', async ({ page }) => {
await page.goto('./', { waitUntil: 'domcontentloaded' });
const folder = await createDomainObjectWithDefaults(page, {
type: 'Folder'
});
await openObjectTreeContextMenu(page, folder.url);
await expect(page.getByLabel('Menu')).toBeVisible();
});
}); });

View File

@ -1,5 +1,5 @@
/***************************************************************************** /*****************************************************************************
* Open MCT, Copyright (c) 2014-2023, United States Government * Open MCT, Copyright (c) 2014-2024, United States Government
* as represented by the Administrator of the National Aeronautics and Space * as represented by the Administrator of the National Aeronautics and Space
* Administration. All rights reserved. * Administration. All rights reserved.
* *
@ -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) (`npm start` and ./e2e/webpack-dev-middleware.js)
*/ */
const { test } = require('../../baseFixtures.js'); import { test } from '../../baseFixtures.js';
test.describe('baseFixtures tests', () => { test.describe('baseFixtures tests', () => {
//Skip this test for now https://github.com/nasa/openmct/issues/6785 //Skip this test for now https://github.com/nasa/openmct/issues/6785

View File

@ -1,5 +1,5 @@
/***************************************************************************** /*****************************************************************************
* Open MCT, Copyright (c) 2014-2023, United States Government * Open MCT, Copyright (c) 2014-2024, United States Government
* as represented by the Administrator of the National Aeronautics and Space * as represented by the Administrator of the National Aeronautics and Space
* Administration. All rights reserved. * Administration. All rights reserved.
* *
@ -45,8 +45,8 @@
*/ */
// Structure: Some standard Imports. Please update the required pathing. // Structure: Some standard Imports. Please update the required pathing.
const { test, expect } = require('../../pluginFixtures'); import { createDomainObjectWithDefaults } from '../../appActions.js';
const { createDomainObjectWithDefaults } = require('../../appActions'); import { expect, test } from '../../pluginFixtures.js';
/** /**
* Structure: * Structure:
@ -164,7 +164,7 @@ async function renameTimerFrom3DotMenu(page, timerUrl, newNameForTimer) {
await page.goto(timerUrl); await page.goto(timerUrl);
// Click on 3 Dot Menu // Click on 3 Dot Menu
await page.locator('button[title="More options"]').click(); await page.locator('button[title="More actions"]').click();
// Click text=Edit Properties... // Click text=Edit Properties...
await page.locator('text=Edit Properties...').click(); await page.locator('text=Edit Properties...').click();

View File

@ -1,5 +1,5 @@
/***************************************************************************** /*****************************************************************************
* Open MCT, Copyright (c) 2014-2022, United States Government * Open MCT, Copyright (c) 2014-2024, United States Government
* as represented by the Administrator of the National Aeronautics and Space * as represented by the Administrator of the National Aeronautics and Space
* Administration. All rights reserved. * Administration. All rights reserved.
* *
@ -19,7 +19,6 @@
* this source code distribution or the Licensing information page available * this source code distribution or the Licensing information page available
* at runtime from the About dialog for additional information. * at runtime from the About dialog for additional information.
*****************************************************************************/ *****************************************************************************/
/* global __dirname */
/** /**
* This test suite is dedicated to generating LocalStorage via Session Storage to be used * This test suite is dedicated to generating LocalStorage via Session Storage to be used
* in some visual test suites like controlledClock.visual.spec.js. This suite should run to completion * in some visual test suites like controlledClock.visual.spec.js. This suite should run to completion
@ -32,13 +31,11 @@
* and is additionally verified in the validation test suites below. * and is additionally verified in the validation test suites below.
*/ */
const { test, expect } = require('../../pluginFixtures.js'); import { fileURLToPath } from 'url';
const {
createDomainObjectWithDefaults, import { createDomainObjectWithDefaults, createExampleTelemetryObject } from '../../appActions.js';
createExampleTelemetryObject import { MISSION_TIME } from '../../constants.js';
} = require('../../appActions.js'); import { expect, test } from '../../pluginFixtures.js';
const { MISSION_TIME } = require('../../constants.js');
const path = require('path');
const overlayPlotName = 'Overlay Plot with Telemetry Object'; const overlayPlotName = 'Overlay Plot with Telemetry Object';
@ -56,29 +53,28 @@ test.describe('Generate Visual Test Data @localStorage @generatedata', () => {
}); });
test('Generate display layout with 2 child display layouts', async ({ page, context }) => { test('Generate display layout with 2 child display layouts', async ({ page, context }) => {
// Create Display Layout
const parent = await createDomainObjectWithDefaults(page, { const parent = await createDomainObjectWithDefaults(page, {
type: 'Display Layout', type: 'Display Layout',
name: 'Parent Display Layout' name: 'Parent Display Layout'
}); });
const child1 = await createDomainObjectWithDefaults(page, { await createDomainObjectWithDefaults(page, {
type: 'Display Layout', type: 'Display Layout',
name: 'Child Layout 1', name: 'Child Layout 1',
parent: parent.uuid parent: parent.uuid
}); });
const child2 = await createDomainObjectWithDefaults(page, { await createDomainObjectWithDefaults(page, {
type: 'Display Layout', type: 'Display Layout',
name: 'Child Layout 2', name: 'Child Layout 2',
parent: parent.uuid parent: parent.uuid
}); });
await page.goto(parent.url); await page.goto(parent.url, { waitUntil: 'domcontentloaded' });
await page.getByLabel('Edit').click(); await page.getByLabel('Edit Object').click();
await page.getByLabel(`${child2.name} Layout Grid`).hover(); await page.getByLabel('Child Layout 2 Layout', { exact: true }).hover();
await page.getByLabel('Move Sub-object Frame').nth(1).click(); await page.getByLabel('Move Sub-object Frame').nth(1).click();
await page.getByLabel('X:').fill('30'); await page.getByLabel('X:').fill('30');
await page.getByLabel(`${child1.name} Layout Grid`).hover(); await page.getByLabel('Child Layout 1 Layout', { exact: true }).hover();
await page.getByLabel('Move Sub-object Frame').first().click(); await page.getByLabel('Move Sub-object Frame').first().click();
await page.getByLabel('Y:').fill('30'); await page.getByLabel('Y:').fill('30');
@ -87,7 +83,9 @@ test.describe('Generate Visual Test Data @localStorage @generatedata', () => {
//Save localStorage for future test execution //Save localStorage for future test execution
await context.storageState({ await context.storageState({
path: path.join(__dirname, '../../../e2e/test-data/display_layout_with_child_layouts.json') path: fileURLToPath(
new URL('../../../e2e/test-data/display_layout_with_child_layouts.json', import.meta.url)
)
}); });
}); });
@ -108,11 +106,13 @@ test.describe('Generate Visual Test Data @localStorage @generatedata', () => {
parent: parent.uuid parent: parent.uuid
}); });
await page.goto(parent.url); await page.goto(parent.url, { waitUntil: 'domcontentloaded' });
//Save localStorage for future test execution //Save localStorage for future test execution
await context.storageState({ await context.storageState({
path: path.join(__dirname, '../../../e2e/test-data/flexible_layout_with_child_layouts.json') path: fileURLToPath(
new URL('../../../e2e/test-data/flexible_layout_with_child_layouts.json', import.meta.url)
)
}); });
}); });
@ -130,10 +130,10 @@ test.describe('Generate Visual Test Data @localStorage @generatedata', () => {
const exampleTelemetry = await createExampleTelemetryObject(page); const exampleTelemetry = await createExampleTelemetryObject(page);
// Make Link from Telemetry Object to Overlay Plot // Make Link from Telemetry Object to Overlay Plot
await page.locator('button[title="More options"]').click(); await page.locator('button[title="More actions"]').click();
// Select 'Create Link' from dropdown // Select 'Create Link' from dropdown
await page.getByRole('menuitem', { name: 'Create Link' }).click(); await page.getByRole('menuitem', { name: 'Create Link' }).click();
// Search and Select for overlay Plot within Create Modal // Search and Select for overlay Plot within Create Modal
await page.getByRole('dialog').getByRole('searchbox', { name: 'Search Input' }).click(); await page.getByRole('dialog').getByRole('searchbox', { name: 'Search Input' }).click();
@ -189,7 +189,9 @@ test.describe('Generate Visual Test Data @localStorage @generatedata', () => {
// Save localStorage for future test execution // Save localStorage for future test execution
await context.storageState({ await context.storageState({
path: path.join(__dirname, '../../../e2e/test-data/overlay_plot_storage.json') path: fileURLToPath(
new URL('../../../e2e/test-data/overlay_plot_storage.json', import.meta.url)
)
}); });
}); });
// TODO: Merge this with previous test. Edit object created in previous test. // TODO: Merge this with previous test. Edit object created in previous test.
@ -203,8 +205,8 @@ test.describe('Generate Visual Test Data @localStorage @generatedata', () => {
const swgWith5sDelay = await createExampleTelemetryObject(page, overlayPlot.uuid); const swgWith5sDelay = await createExampleTelemetryObject(page, overlayPlot.uuid);
await page.goto(swgWith5sDelay.url); await page.goto(swgWith5sDelay.url);
await page.getByTitle('More options').click(); await page.getByLabel('More actions').click();
await page.getByRole('menuitem', { name: ' Edit Properties...' }).click(); await page.getByLabel('Edit Properties...').click();
//Edit Example Telemetry Object to include 5s loading Delay //Edit Example Telemetry Object to include 5s loading Delay
await page.locator('[aria-label="Loading Delay \\(ms\\)"]').fill('5000'); await page.locator('[aria-label="Loading Delay \\(ms\\)"]').fill('5000');
@ -223,17 +225,21 @@ test.describe('Generate Visual Test Data @localStorage @generatedata', () => {
// Clear Recently Viewed // Clear Recently Viewed
await page.getByRole('button', { name: 'Clear Recently Viewed' }).click(); await page.getByRole('button', { name: 'Clear Recently Viewed' }).click();
await page.getByRole('button', { name: 'OK' }).click(); await page.getByRole('button', { name: 'OK', exact: true }).click();
//Save localStorage for future test execution //Save localStorage for future test execution
await context.storageState({ await context.storageState({
path: path.join(__dirname, '../../../e2e/test-data/overlay_plot_with_delay_storage.json') path: fileURLToPath(
new URL('../../../e2e/test-data/overlay_plot_with_delay_storage.json', import.meta.url)
)
}); });
}); });
}); });
test.describe('Validate Overlay Plot with Telemetry Object @localStorage @generatedata', () => { test.describe('Validate Overlay Plot with Telemetry Object @localStorage @generatedata', () => {
test.use({ test.use({
storageState: path.join(__dirname, '../../../e2e/test-data/overlay_plot_storage.json') storageState: fileURLToPath(
new URL('../../../e2e/test-data/overlay_plot_storage.json', import.meta.url)
)
}); });
test('Validate Overlay Plot with Telemetry Object', async ({ page }) => { test('Validate Overlay Plot with Telemetry Object', async ({ page }) => {
await page.goto('./', { waitUntil: 'domcontentloaded' }); await page.goto('./', { waitUntil: 'domcontentloaded' });
@ -275,9 +281,8 @@ test.describe('Validate Overlay Plot with Telemetry Object @localStorage @genera
test.describe('Validate Overlay Plot with 5s Delay Telemetry Object @localStorage @generatedata', () => { test.describe('Validate Overlay Plot with 5s Delay Telemetry Object @localStorage @generatedata', () => {
test.use({ test.use({
storageState: path.join( storageState: fileURLToPath(
__dirname, new URL('../../../e2e/test-data/overlay_plot_with_delay_storage.json', import.meta.url)
'../../../e2e/test-data/overlay_plot_with_delay_storage.json'
) )
}); });
test('Validate Overlay Plot with Telemetry Object', async ({ page }) => { test('Validate Overlay Plot with Telemetry Object', async ({ page }) => {

View File

@ -1,5 +1,5 @@
/***************************************************************************** /*****************************************************************************
* Open MCT, Copyright (c) 2014-2023, United States Government * Open MCT, Copyright (c) 2014-2024, United States Government
* as represented by the Administrator of the National Aeronautics and Space * as represented by the Administrator of the National Aeronautics and Space
* Administration. All rights reserved. * Administration. All rights reserved.
* *
@ -25,7 +25,7 @@ This test suite is dedicated to testing our use of our custom fixtures to verify
that they are working as expected. that they are working as expected.
*/ */
const { test } = require('../../pluginFixtures.js'); import { test } from '../../pluginFixtures.js';
// eslint-disable-next-line playwright/no-skipped-test // eslint-disable-next-line playwright/no-skipped-test
test.describe.skip('pluginFixtures tests', () => { test.describe.skip('pluginFixtures tests', () => {

View File

@ -1,5 +1,5 @@
/***************************************************************************** /*****************************************************************************
* Open MCT, Copyright (c) 2014-2023, United States Government * Open MCT, Copyright (c) 2014-2024, United States Government
* as represented by the Administrator of the National Aeronautics and Space * as represented by the Administrator of the National Aeronautics and Space
* Administration. All rights reserved. * Administration. All rights reserved.
* *
@ -24,42 +24,36 @@
This test suite is dedicated to tests which verify branding related components. This test suite is dedicated to tests which verify branding related components.
*/ */
const { test, expect } = require('../../baseFixtures.js'); import { expect, test } from '../../baseFixtures.js';
test.describe('Branding tests', () => { test.describe('Branding tests', () => {
test('About Modal launches with basic branding properties', async ({ page }) => { test.beforeEach(async ({ page }) => {
// Go to baseURL
await page.goto('./', { waitUntil: 'domcontentloaded' }); await page.goto('./', { waitUntil: 'domcontentloaded' });
});
// Click About button test('About Modal launches with basic branding properties', async ({ page }) => {
await page.click('.l-shell__app-logo'); await page.getByLabel('About Modal').click();
// Verify that the NASA Logo Appears // Verify that the NASA Logo Appears
await expect(page.locator('.c-about__image')).toBeVisible(); await expect(page.getByAltText('Open MCT Splash Logo')).toBeVisible();
// Modify the Build information in 'about' Modal // Modify the Build information in 'about' Modal
const versionInformationLocator = page.locator('ul.t-info.l-info.s-info').first(); await expect.soft(page.getByLabel('Version Number')).toContainText(/Version: \d/);
await expect(versionInformationLocator).toBeEnabled();
await expect.soft(versionInformationLocator).toContainText(/Version: \d/);
await expect await expect
.soft(versionInformationLocator) .soft(page.getByLabel('Build Date'))
.toContainText(/Build Date: ((?:Mon|Tue|Wed|Thu|Fri|Sat|Sun))/); .toContainText(/Build Date: ((?:Mon|Tue|Wed|Thu|Fri|Sat|Sun))/);
await expect.soft(versionInformationLocator).toContainText(/Revision: \b[0-9a-f]{5,40}\b/); await expect.soft(page.getByLabel('Revision')).toContainText(/Revision: \b[0-9a-f]{5,40}\b/);
await expect.soft(versionInformationLocator).toContainText(/Branch: ./); await expect.soft(page.getByLabel('Branch')).toContainText(/Branch: ./);
}); });
test('Verify Links in About Modal @2p', async ({ page }) => { test('Verify Links in About Modal @2p', async ({ page }) => {
// Go to baseURL
await page.goto('./', { waitUntil: 'domcontentloaded' });
// Click About button // Click About button
await page.click('.l-shell__app-logo'); await page.getByLabel('About Modal').click();
// Verify that clicking on the third party licenses information opens up another tab on licenses url // Verify that clicking on the third party licenses information opens up another tab on licenses url
const [page2] = await Promise.all([ const [page2] = await Promise.all([
page.waitForEvent('popup'), page.waitForEvent('popup'),
page.locator('text=click here for third party licensing information').click() page.getByText('click here for third party licensing information').click()
]); ]);
await page2.waitForLoadState('networkidle'); //Avoids timing issues with juggler/firefox await page2.waitForLoadState('domcontentloaded'); //Avoids timing issues with juggler/firefox
expect(page2.waitForURL('**/licenses**')).toBeTruthy(); expect(page2.waitForURL('**/licenses**')).toBeTruthy();
}); });
}); });

View File

@ -1,5 +1,5 @@
/***************************************************************************** /*****************************************************************************
* Open MCT, Copyright (c) 2014-2023, United States Government * Open MCT, Copyright (c) 2014-2024, United States Government
* as represented by the Administrator of the National Aeronautics and Space * as represented by the Administrator of the National Aeronautics and Space
* Administration. All rights reserved. * Administration. All rights reserved.
* *
@ -24,8 +24,8 @@
Verify that the "Clear Data" menu action performs as expected for various object types. Verify that the "Clear Data" menu action performs as expected for various object types.
*/ */
const { test, expect } = require('../../pluginFixtures.js'); import { createDomainObjectWithDefaults } from '../../appActions.js';
const { createDomainObjectWithDefaults } = require('../../appActions.js'); import { expect, test } from '../../pluginFixtures.js';
const backgroundImageSelector = '.c-imagery__main-image__background-image'; const backgroundImageSelector = '.c-imagery__main-image__background-image';
@ -43,17 +43,22 @@ test.describe('Clear Data Action', () => {
await expect(page.locator(backgroundImageSelector)).toBeVisible(); await expect(page.locator(backgroundImageSelector)).toBeVisible();
}); });
test('works as expected with Example Imagery', async ({ page }) => { test('works as expected with Example Imagery', async ({ page }) => {
await expect(await page.locator('.c-thumb__image').count()).toBeGreaterThan(0); expect(await page.locator('.c-thumb__image').count()).toBeGreaterThan(0);
// Click the "Clear Data" menu action // Click the "Clear Data" menu action
await page.getByTitle('More options').click(); await page.getByTitle('More actions').click();
const clearDataMenuItem = page.getByRole('menuitem', { await expect(
name: 'Clear Data' page.getByRole('menuitem', {
}); name: 'Clear Data for Object'
await expect(clearDataMenuItem).toBeEnabled(); })
await clearDataMenuItem.click(); ).toBeEnabled();
await page
.getByRole('menuitem', {
name: 'Clear Data for Object'
})
.click();
// Verify that the background image is no longer visible // Verify that the background image is no longer visible
await expect(page.locator(backgroundImageSelector)).toBeHidden(); await expect(page.locator(backgroundImageSelector)).toBeHidden();
await expect(await page.locator('.c-thumb__image').count()).toBe(0); expect(await page.locator('.c-thumb__image').count()).toBe(0);
}); });
}); });

View File

@ -1,5 +1,5 @@
/***************************************************************************** /*****************************************************************************
* Open MCT, Copyright (c) 2014-2023, United States Government * Open MCT, Copyright (c) 2014-2024, United States Government
* as represented by the Administrator of the National Aeronautics and Space * as represented by the Administrator of the National Aeronautics and Space
* Administration. All rights reserved. * Administration. All rights reserved.
* *
@ -25,7 +25,7 @@
* *
*/ */
const { test, expect } = require('../../pluginFixtures'); import { expect, test } from '../../pluginFixtures.js';
test.describe('CouchDB Status Indicator with mocked responses @couchdb', () => { test.describe('CouchDB Status Indicator with mocked responses @couchdb', () => {
test.use({ failOnConsoleError: false }); test.use({ failOnConsoleError: false });

View File

@ -1,5 +1,5 @@
/***************************************************************************** /*****************************************************************************
* Open MCT, Copyright (c) 2014-2023, United States Government * Open MCT, Copyright (c) 2014-2024, United States Government
* as represented by the Administrator of the National Aeronautics and Space * as represented by the Administrator of the National Aeronautics and Space
* Administration. All rights reserved. * Administration. All rights reserved.
* *
@ -24,8 +24,8 @@
This test suite is dedicated to tests which verify the basic operations surrounding the example event generator. This test suite is dedicated to tests which verify the basic operations surrounding the example event generator.
*/ */
const { test, expect } = require('../../../pluginFixtures'); import { createDomainObjectWithDefaults } from '../../../appActions.js';
const { createDomainObjectWithDefaults } = require('../../../appActions'); import { expect, test } from '../../../pluginFixtures.js';
test.describe('Example Event Generator CRUD Operations', () => { test.describe('Example Event Generator CRUD Operations', () => {
test('Can create a Test Event Generator and it results in the table View', async ({ page }) => { test('Can create a Test Event Generator and it results in the table View', async ({ page }) => {

View File

@ -1,5 +1,5 @@
/***************************************************************************** /*****************************************************************************
* Open MCT, Copyright (c) 2014-2023, United States Government * Open MCT, Copyright (c) 2014-2024, United States Government
* as represented by the Administrator of the National Aeronautics and Space * as represented by the Administrator of the National Aeronautics and Space
* Administration. All rights reserved. * Administration. All rights reserved.
* *
@ -24,7 +24,7 @@
This test suite is dedicated to tests which verify the basic operations surrounding conditionSets. This test suite is dedicated to tests which verify the basic operations surrounding conditionSets.
*/ */
const { test, expect } = require('../../../../baseFixtures'); import { expect, test } from '../../../../baseFixtures.js';
test.describe('Sine Wave Generator', () => { test.describe('Sine Wave Generator', () => {
test('Create new Sine Wave Generator Object and validate create Form Logic', async ({ test('Create new Sine Wave Generator Object and validate create Form Logic', async ({
@ -38,7 +38,7 @@ test.describe('Sine Wave Generator', () => {
await page.goto('./', { waitUntil: 'domcontentloaded' }); await page.goto('./', { waitUntil: 'domcontentloaded' });
//Click the Create button //Click the Create button
await page.click('button:has-text("Create")'); await page.getByRole('button', { name: 'Create' }).click();
// Click Sine Wave Generator // Click Sine Wave Generator
await page.click('text=Sine Wave Generator'); await page.click('text=Sine Wave Generator');

View File

@ -1,5 +1,5 @@
/***************************************************************************** /*****************************************************************************
* Open MCT, Copyright (c) 2014-2023, United States Government * Open MCT, Copyright (c) 2014-2024, United States Government
* as represented by the Administrator of the National Aeronautics and Space * as represented by the Administrator of the National Aeronautics and Space
* Administration. All rights reserved. * Administration. All rights reserved.
* *
@ -19,15 +19,16 @@
* this source code distribution or the Licensing information page available * this source code distribution or the Licensing information page available
* at runtime from the About dialog for additional information. * at runtime from the About dialog for additional information.
*****************************************************************************/ *****************************************************************************/
/* global __dirname */
/* /*
This test suite is dedicated to tests which verify form functionality in isolation This test suite is dedicated to tests which verify form functionality in isolation
*/ */
const { test, expect } = require('../../pluginFixtures'); import { fileURLToPath } from 'url';
const { createDomainObjectWithDefaults } = require('../../appActions'); import { v4 as genUuid } from 'uuid';
const genUuid = require('uuid').v4;
const path = require('path'); import { createDomainObjectWithDefaults } from '../../appActions.js';
import { expect, test } from '../../pluginFixtures.js';
const TEST_FOLDER = 'test folder'; const TEST_FOLDER = 'test folder';
const jsonFilePath = 'e2e/test-data/ExampleLayouts.json'; const jsonFilePath = 'e2e/test-data/ExampleLayouts.json';
@ -40,8 +41,8 @@ test.describe('Form Validation Behavior', () => {
//Go to baseURL //Go to baseURL
await page.goto('./', { waitUntil: 'domcontentloaded' }); await page.goto('./', { waitUntil: 'domcontentloaded' });
await page.click('button:has-text("Create")'); await page.getByRole('button', { name: 'Create' }).click();
await page.getByRole('menuitem', { name: 'Folder' }).click(); await page.getByRole('menuitem', { name: 'Folder' }).click();
// Fill in empty string into title and trigger validation with 'Tab' // Fill in empty string into title and trigger validation with 'Tab'
await page.click('text=Properties Title Notes >> input[type="text"]'); await page.click('text=Properties Title Notes >> input[type="text"]');
@ -72,14 +73,14 @@ test.describe('Form Validation Behavior', () => {
test.describe('Form File Input Behavior', () => { test.describe('Form File Input Behavior', () => {
test.beforeEach(async ({ page }) => { test.beforeEach(async ({ page }) => {
await page.addInitScript({ await page.addInitScript({
path: path.join(__dirname, '../../helper', 'addInitFileInputObject.js') path: fileURLToPath(new URL('../../helper/addInitFileInputObject.js', import.meta.url))
}); });
}); });
test('Can select a JSON file type', async ({ page }) => { test('Can select a JSON file type', async ({ page }) => {
await page.goto('./', { waitUntil: 'domcontentloaded' }); await page.goto('./', { waitUntil: 'domcontentloaded' });
await page.getByRole('button', { name: 'Create' }).click(); await page.getByRole('button', { name: 'Create' }).click();
await page.getByRole('menuitem', { name: 'JSON File Input Object' }).click(); await page.getByRole('menuitem', { name: 'JSON File Input Object' }).click();
await page.setInputFiles('#fileElem', jsonFilePath); await page.setInputFiles('#fileElem', jsonFilePath);
@ -93,7 +94,7 @@ test.describe('Form File Input Behavior', () => {
test('Can select an image file type', async ({ page }) => { test('Can select an image file type', async ({ page }) => {
await page.goto('./', { waitUntil: 'domcontentloaded' }); await page.goto('./', { waitUntil: 'domcontentloaded' });
await page.getByRole('button', { name: 'Create' }).click(); await page.getByRole('button', { name: 'Create' }).click();
await page.getByRole('menuitem', { name: 'Image File Input Object' }).click(); await page.getByRole('menuitem', { name: 'Image File Input Object' }).click();
await page.setInputFiles('#fileElem', imageFilePath); await page.setInputFiles('#fileElem', imageFilePath);
@ -109,7 +110,7 @@ test.describe('Persistence operations @addInit', () => {
// add non persistable root item // add non persistable root item
test.beforeEach(async ({ page }) => { test.beforeEach(async ({ page }) => {
await page.addInitScript({ await page.addInitScript({
path: path.join(__dirname, '../../helper', 'addNoneditableObject.js') path: fileURLToPath(new URL('../../helper/addNoneditableObject.js', import.meta.url))
}); });
}); });
@ -120,7 +121,7 @@ test.describe('Persistence operations @addInit', () => {
}); });
await page.goto('./', { waitUntil: 'domcontentloaded' }); await page.goto('./', { waitUntil: 'domcontentloaded' });
await page.click('button:has-text("Create")'); await page.getByRole('button', { name: 'Create' }).click();
await page.click('text=Condition Set'); await page.click('text=Condition Set');
@ -157,7 +158,7 @@ test.describe('Persistence operations @couchdb', () => {
}); });
// Open the edit form for the clock object // Open the edit form for the clock object
await page.click('button[title="More options"]'); await page.click('button[title="More actions"]');
await page.click('li[title="Edit properties of this object."]'); await page.click('li[title="Edit properties of this object."]');
// Modify the display format from default 12hr -> 24hr and click 'Save' // Modify the display format from default 12hr -> 24hr and click 'Save'

View File

@ -1,5 +1,5 @@
/***************************************************************************** /*****************************************************************************
* Open MCT, Copyright (c) 2014-2023, United States Government * Open MCT, Copyright (c) 2014-2024, United States Government
* as represented by the Administrator of the National Aeronautics and Space * as represented by the Administrator of the National Aeronautics and Space
* Administration. All rights reserved. * Administration. All rights reserved.
* *
@ -19,20 +19,20 @@
* this source code distribution or the Licensing information page available * this source code distribution or the Licensing information page available
* at runtime from the About dialog for additional information. * at runtime from the About dialog for additional information.
*****************************************************************************/ *****************************************************************************/
/* global __dirname */
/* /*
This test suite is dedicated to tests which verify persistability checks This test suite is dedicated to tests which verify persistability checks
*/ */
const { test, expect } = require('../../baseFixtures.js'); import { fileURLToPath } from 'url';
const path = require('path'); import { expect, test } from '../../baseFixtures.js';
test.describe('Persistence operations @addInit', () => { test.describe('Persistence operations @addInit', () => {
// add non persistable root item // add non persistable root item
test.beforeEach(async ({ page }) => { test.beforeEach(async ({ page }) => {
await page.addInitScript({ await page.addInitScript({
path: path.join(__dirname, '../../helper', 'addNoneditableObject.js') path: fileURLToPath(new URL('../../helper/addNoneditableObject.js', import.meta.url))
}); });
}); });

View File

@ -0,0 +1,127 @@
/*****************************************************************************
* Open MCT, Copyright (c) 2014-2024, United States Government
* as represented by the Administrator of the National Aeronautics and Space
* Administration. All rights reserved.
*
* Open MCT is licensed under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0.
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*
* Open MCT includes source code licensed under additional open source
* licenses. See the Open Source Licenses file (LICENSES.md) included with
* this source code distribution or the Licensing information page available
* at runtime from the About dialog for additional information.
*****************************************************************************/
/*
This test suite is dedicated to tests which verify persistability checks
*/
import { fileURLToPath } from 'url';
import { expect, test } from '../../baseFixtures.js';
test.describe('Mission Status @addInit', () => {
const NO_GO = '0';
const GO = '1';
test.beforeEach(async ({ page }) => {
// FIXME: determine if plugins will be added to index.html or need to be injected
await page.addInitScript({
path: fileURLToPath(new URL('../../helper/addInitExampleUser.js', import.meta.url))
});
await page.goto('./', { waitUntil: 'domcontentloaded' });
await expect(page.getByText('Select Role')).toBeVisible();
// Description should be empty https://github.com/nasa/openmct/issues/6978
await expect(page.getByLabel('Dialog message')).toBeHidden();
// set role
await page.getByRole('button', { name: 'Select', exact: true }).click();
// dismiss role confirmation popup
await page.getByRole('button', { name: 'Dismiss' }).click();
});
test('Basic functionality', async ({ page }) => {
const imageryStatusSelect = page.getByRole('combobox', { name: 'Imagery' });
const commandingStatusSelect = page.getByRole('combobox', { name: 'Commanding' });
const drivingStatusSelect = page.getByRole('combobox', { name: 'Driving' });
const missionStatusPanel = page.getByRole('dialog', { name: 'User Control Panel' });
await test.step('Mission status panel shows/hides when toggled', async () => {
// Ensure that clicking the button toggles the dialog
await page.getByLabel('Toggle Mission Status Panel').click();
await expect(missionStatusPanel).toBeVisible();
await page.getByLabel('Toggle Mission Status Panel').click();
await expect(missionStatusPanel).toBeHidden();
await page.getByLabel('Toggle Mission Status Panel').click();
await expect(missionStatusPanel).toBeVisible();
// Ensure that clicking the close button closes the dialog
await page.getByLabel('Close Mission Status Panel').click();
await expect(missionStatusPanel).toBeHidden();
await page.getByLabel('Toggle Mission Status Panel').click();
await expect(missionStatusPanel).toBeVisible();
// Ensure clicking off the dialog also closes it
await page.getByLabel('My Items Grid View').click();
await expect(missionStatusPanel).toBeHidden();
await page.getByLabel('Toggle Mission Status Panel').click();
await expect(missionStatusPanel).toBeVisible();
});
await test.step('Mission action statuses have correct defaults and can be set', async () => {
await expect(imageryStatusSelect).toHaveValue(NO_GO);
await expect(commandingStatusSelect).toHaveValue(NO_GO);
await expect(drivingStatusSelect).toHaveValue(NO_GO);
await setMissionStatus(page, 'Imagery', GO);
await expect(imageryStatusSelect).toHaveValue(GO);
await expect(commandingStatusSelect).toHaveValue(NO_GO);
await expect(drivingStatusSelect).toHaveValue(NO_GO);
await setMissionStatus(page, 'Commanding', GO);
await expect(imageryStatusSelect).toHaveValue(GO);
await expect(commandingStatusSelect).toHaveValue(GO);
await expect(drivingStatusSelect).toHaveValue(NO_GO);
await setMissionStatus(page, 'Driving', GO);
await expect(imageryStatusSelect).toHaveValue(GO);
await expect(commandingStatusSelect).toHaveValue(GO);
await expect(drivingStatusSelect).toHaveValue(GO);
await setMissionStatus(page, 'Imagery', NO_GO);
await expect(imageryStatusSelect).toHaveValue(NO_GO);
await expect(commandingStatusSelect).toHaveValue(GO);
await expect(drivingStatusSelect).toHaveValue(GO);
await setMissionStatus(page, 'Commanding', NO_GO);
await expect(imageryStatusSelect).toHaveValue(NO_GO);
await expect(commandingStatusSelect).toHaveValue(NO_GO);
await expect(drivingStatusSelect).toHaveValue(GO);
await setMissionStatus(page, 'Driving', NO_GO);
await expect(imageryStatusSelect).toHaveValue(NO_GO);
await expect(commandingStatusSelect).toHaveValue(NO_GO);
await expect(drivingStatusSelect).toHaveValue(NO_GO);
});
});
});
/**
*
* @param {import('@playwright/test').Page} page
* @param {'Commanding'|'Imagery'|'Driving'} action
* @param {'0'|'1'} status
*/
async function setMissionStatus(page, action, status) {
await page.getByRole('combobox', { name: action }).selectOption(status);
await expect(
page.getByRole('alert').filter({ hasText: 'Successfully set mission status' })
).toBeVisible();
await page.getByLabel('Dismiss').click();
}

View File

@ -1,5 +1,5 @@
/***************************************************************************** /*****************************************************************************
* Open MCT, Copyright (c) 2014-2023, United States Government * Open MCT, Copyright (c) 2014-2024, United States Government
* as represented by the Administrator of the National Aeronautics and Space * as represented by the Administrator of the National Aeronautics and Space
* Administration. All rights reserved. * Administration. All rights reserved.
* *
@ -24,8 +24,8 @@
This test suite is dedicated to tests which verify the basic operations surrounding moving & linking objects. This test suite is dedicated to tests which verify the basic operations surrounding moving & linking objects.
*/ */
const { test, expect } = require('../../pluginFixtures'); import { createDomainObjectWithDefaults } from '../../appActions.js';
const { createDomainObjectWithDefaults } = require('../../appActions'); import { expect, test } from '../../pluginFixtures.js';
test.describe('Move & link item tests', () => { test.describe('Move & link item tests', () => {
test('Create a basic object and verify that it can be moved to another folder', async ({ test('Create a basic object and verify that it can be moved to another folder', async ({

View File

@ -1,5 +1,5 @@
/***************************************************************************** /*****************************************************************************
* Open MCT, Copyright (c) 2014-2023, United States Government * Open MCT, Copyright (c) 2014-2024, United States Government
* as represented by the Administrator of the National Aeronautics and Space * as represented by the Administrator of the National Aeronautics and Space
* Administration. All rights reserved. * Administration. All rights reserved.
* *
@ -24,8 +24,8 @@
This test suite is dedicated to tests which verify Open MCT's Notification functionality This test suite is dedicated to tests which verify Open MCT's Notification functionality
*/ */
const { createDomainObjectWithDefaults, createNotification } = require('../../appActions'); import { createDomainObjectWithDefaults, createNotification } from '../../appActions.js';
const { test, expect } = require('../../pluginFixtures'); import { expect, test } from '../../pluginFixtures.js';
test.describe('Notifications List', () => { test.describe('Notifications List', () => {
test.fixme('Notifications can be dismissed individually', async ({ page }) => { test.fixme('Notifications can be dismissed individually', async ({ page }) => {
@ -91,27 +91,30 @@ test.describe('Notification Overlay', () => {
// Create a new Display Layout object // Create a new Display Layout object
await createDomainObjectWithDefaults(page, { type: 'Display Layout' }); await createDomainObjectWithDefaults(page, { type: 'Display Layout' });
// Dismiss notification banner
await page.getByRole('button', { name: 'Dismiss' }).click();
// Click on the button "Review 1 Notification" // Click on the button "Review 1 Notification"
await page.click('button[aria-label="Review 1 Notification"]'); await page.getByRole('button', { name: 'Review 1 Notification' }).click();
// Verify that Notification List is open // Verify that Notification List is open
expect(await page.locator('div[role="dialog"]').isVisible()).toBe(true); await expect(page.getByRole('dialog', { name: 'Overlay' })).toBeVisible();
// Wait until there is no Notification Banner // Wait until there is no Notification Banner
await page.waitForSelector('div[role="alert"]', { state: 'detached' }); await expect(page.getByRole('alert')).not.toBeAttached();
// Click on the "Close" button of the Notification List // Click on the "Close" button of the Notification List
await page.click('button[aria-label="Close"]'); await page.getByRole('button', { name: 'Close' }).click();
// On the Display Layout object, click on the "Edit" button // On the Display Layout object, click on the "Edit" button
await page.click('button[title="Edit"]'); await page.getByRole('button', { name: 'Edit Object' }).click();
// Click on the "Save" button // Click on the "Save" button
await page.click('button[title="Save"]'); await page.getByRole('button', { name: 'Save' }).click();
await page.getByRole('listitem', { name: 'Save and Finish Editing' }).click(); await page.getByRole('listitem', { name: 'Save and Finish Editing' }).click();
// Verify that Notification List is NOT open // Verify that Notification List is NOT open
expect(await page.locator('div[role="dialog"]').isVisible()).toBe(false); await expect(page.getByRole('dialog', { name: 'Overlay' })).toBeHidden();
}); });
}); });

View File

@ -1,5 +1,5 @@
/***************************************************************************** /*****************************************************************************
* Open MCT, Copyright (c) 2014-2023, United States Government * Open MCT, Copyright (c) 2014-2024, United States Government
* as represented by the Administrator of the National Aeronautics and Space * as represented by the Administrator of the National Aeronautics and Space
* Administration. All rights reserved. * Administration. All rights reserved.
* *
@ -19,15 +19,26 @@
* this source code distribution or the Licensing information page available * this source code distribution or the Licensing information page available
* at runtime from the About dialog for additional information. * at runtime from the About dialog for additional information.
*****************************************************************************/ *****************************************************************************/
const { test, expect } = require('../../../pluginFixtures'); import fs from 'fs';
const { createPlanFromJSON, createDomainObjectWithDefaults } = require('../../../appActions');
const testPlan1 = require('../../../test-data/examplePlans/ExamplePlan_Small1.json'); import { getPreciseDuration } from '../../../../src/utils/duration.js';
const testPlan2 = require('../../../test-data/examplePlans/ExamplePlan_Small2.json'); import { createDomainObjectWithDefaults, createPlanFromJSON } from '../../../appActions.js';
const { import {
assertPlanActivities, assertPlanActivities,
setBoundsToSpanAllActivities setBoundsToSpanAllActivities
} = require('../../../helper/planningUtils'); } from '../../../helper/planningUtils.js';
const { getPreciseDuration } = require('../../../../src/utils/duration'); import { expect, test } from '../../../pluginFixtures.js';
const testPlan1 = JSON.parse(
fs.readFileSync(
new URL('../../../test-data/examplePlans/ExamplePlan_Small1.json', import.meta.url)
)
);
const testPlan2 = JSON.parse(
fs.readFileSync(
new URL('../../../test-data/examplePlans/ExamplePlan_Small2.json', import.meta.url)
)
);
test.describe('Gantt Chart', () => { test.describe('Gantt Chart', () => {
let ganttChart; let ganttChart;
@ -58,7 +69,7 @@ test.describe('Gantt Chart', () => {
.getByRole('dialog') .getByRole('dialog')
.filter({ hasText: 'This action will replace the current Plan. Do you want to continue?' }); .filter({ hasText: 'This action will replace the current Plan. Do you want to continue?' });
await expect(replaceModal).toBeVisible(); await expect(replaceModal).toBeVisible();
await page.getByRole('button', { name: 'OK' }).click(); await page.getByRole('button', { name: 'Ok', exact: true }).click();
await assertPlanActivities(page, testPlan2, ganttChart.url); await assertPlanActivities(page, testPlan2, ganttChart.url);
}); });

View File

@ -1,5 +1,5 @@
/***************************************************************************** /*****************************************************************************
* Open MCT, Copyright (c) 2014-2023, United States Government * Open MCT, Copyright (c) 2014-2024, United States Government
* as represented by the Administrator of the National Aeronautics and Space * as represented by the Administrator of the National Aeronautics and Space
* Administration. All rights reserved. * Administration. All rights reserved.
* *
@ -19,15 +19,27 @@
* this source code distribution or the Licensing information page available * this source code distribution or the Licensing information page available
* at runtime from the About dialog for additional information. * at runtime from the About dialog for additional information.
*****************************************************************************/ *****************************************************************************/
const { test } = require('../../../pluginFixtures'); import fs from 'fs';
const { createPlanFromJSON } = require('../../../appActions');
const { addPlanGetInterceptor } = require('../../../helper/planningUtils.js'); import { createPlanFromJSON } from '../../../appActions.js';
const testPlan1 = require('../../../test-data/examplePlans/ExamplePlan_Small1.json'); import {
const testPlanWithOrderedLanes = require('../../../test-data/examplePlans/ExamplePlanWithOrderedLanes.json'); addPlanGetInterceptor,
const {
assertPlanActivities, assertPlanActivities,
assertPlanOrderedSwimLanes assertPlanOrderedSwimLanes
} = require('../../../helper/planningUtils'); } from '../../../helper/planningUtils.js';
import { expect, test } from '../../../pluginFixtures.js';
const testPlan1 = JSON.parse(
fs.readFileSync(
new URL('../../../test-data/examplePlans/ExamplePlan_Small1.json', import.meta.url)
)
);
const testPlanWithOrderedLanes = JSON.parse(
fs.readFileSync(
new URL('../../../test-data/examplePlans/ExamplePlanWithOrderedLanes.json', import.meta.url)
)
);
test.describe('Plan', () => { test.describe('Plan', () => {
let plan; let plan;
@ -51,4 +63,47 @@ test.describe('Plan', () => {
}); });
await assertPlanOrderedSwimLanes(page, testPlanWithOrderedLanes, planWithSwimLanes.url); await assertPlanOrderedSwimLanes(page, testPlanWithOrderedLanes, planWithSwimLanes.url);
}); });
test('Allows setting the state of an activity when selected.', async ({ page }) => {
const groups = Object.keys(testPlan1);
const firstGroupKey = groups[0];
const firstGroupItems = testPlan1[firstGroupKey];
const firstActivity = firstGroupItems[0];
const lastActivity = firstGroupItems[firstGroupItems.length - 1];
const startBound = firstActivity.start;
// Set the endBound to the end time of the current activity
let endBound = lastActivity.end;
// eslint-disable-next-line playwright/no-conditional-in-test
if (endBound === startBound) {
// Prevent oddities with setting start and end bound equal
// via URL params
endBound += 1;
}
// 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`
);
// select the first activity in the list
await page.getByText('Past event 1').click();
// Find the activity state section in the inspector
await page.getByRole('tab', { name: 'Activity' }).click();
// Check that activity state dropdown selection shows the `set status` option by default
await expect(page.getByLabel('Activity Status').locator("[aria-selected='true']")).toHaveText(
'Not started'
);
// Change the selection of the activity status
await page.getByRole('combobox').selectOption({ label: 'Aborted' });
// select a different activity and back to the previous one
await page.getByText('Past event 2').click();
await page.getByText('Past event 1').click();
// Check that activity state dropdown selection shows the previously selected option by default
await expect(page.getByLabel('Activity Status').locator("[aria-selected='true']")).toHaveText(
'Aborted'
);
});
}); });

View File

@ -1,5 +1,5 @@
/***************************************************************************** /*****************************************************************************
* Open MCT, Copyright (c) 2014-2023, United States Government * Open MCT, Copyright (c) 2014-2024, United States Government
* as represented by the Administrator of the National Aeronautics and Space * as represented by the Administrator of the National Aeronautics and Space
* Administration. All rights reserved. * Administration. All rights reserved.
* *
@ -19,57 +19,34 @@
* this source code distribution or the Licensing information page available * this source code distribution or the Licensing information page available
* at runtime from the About dialog for additional information. * at runtime from the About dialog for additional information.
*****************************************************************************/ *****************************************************************************/
import fs from 'fs';
const { test, expect } = require('../../../pluginFixtures'); import { createDomainObjectWithDefaults, createPlanFromJSON } from '../../../appActions.js';
const { createDomainObjectWithDefaults, createPlanFromJSON } = require('../../../appActions'); import { getEarliestStartTime } from '../../../helper/planningUtils';
import { expect, test } from '../../../pluginFixtures.js';
const testPlan = { const examplePlanSmall3 = JSON.parse(
TEST_GROUP: [ fs.readFileSync(
{ new URL('../../../test-data/examplePlans/ExamplePlan_Small3.json', import.meta.url)
name: 'Past event 1', )
start: 1660320408000, );
end: 1660343797000, const examplePlanSmall1 = JSON.parse(
type: 'TEST-GROUP', fs.readFileSync(
color: 'orange', new URL('../../../test-data/examplePlans/ExamplePlan_Small1.json', import.meta.url)
textColor: 'white' )
}, );
{ // eslint-disable-next-line no-unused-vars
name: 'Past event 2', const START_TIME_COLUMN = 0;
start: 1660406808000, // eslint-disable-next-line no-unused-vars
end: 1660429160000, const END_TIME_COLUMN = 1;
type: 'TEST-GROUP', const TIME_TO_FROM_COLUMN = 2;
color: 'orange', // eslint-disable-next-line no-unused-vars
textColor: 'white' const ACTIVITY_COLUMN = 3;
}, const HEADER_ROW = 0;
{ const NUM_COLUMNS = 5;
name: 'Past event 3',
start: 1660493208000,
end: 1660503981000,
type: 'TEST-GROUP',
color: 'orange',
textColor: 'white'
},
{
name: 'Past event 4',
start: 1660579608000,
end: 1660624108000,
type: 'TEST-GROUP',
color: 'orange',
textColor: 'white'
},
{
name: 'Past event 5',
start: 1660666008000,
end: 1660681529000,
type: 'TEST-GROUP',
color: 'orange',
textColor: 'white'
}
]
};
test.describe('Time List', () => { test.describe('Time List', () => {
test('Create a Time List, add a single Plan to it and verify all the activities are displayed with no milliseconds', async ({ test("Create a Time List, add a single Plan to it, verify all the activities are displayed with no milliseconds and selecting an activity shows it's properties", async ({
page page
}) => { }) => {
// Goto baseURL // Goto baseURL
@ -84,21 +61,18 @@ test.describe('Time List', () => {
}); });
await test.step('Create a Plan and add it to the timelist', async () => { await test.step('Create a Plan and add it to the timelist', async () => {
const createdPlan = await createPlanFromJSON(page, { await createPlanFromJSON(page, {
name: 'Test Plan', name: 'Test Plan',
json: testPlan json: examplePlanSmall1,
parent: timelist.uuid
}); });
const groups = Object.keys(examplePlanSmall1);
await page.goto(timelist.url); const firstGroupKey = groups[0];
// Expand the tree to show the plan const firstGroupItems = examplePlanSmall1[firstGroupKey];
await page.click("button[title='Show selected item in tree']"); const firstActivity = firstGroupItems[0];
await page.dragAndDrop(`role=treeitem[name=/${createdPlan.name}/]`, '.c-object-view'); const lastActivity = firstGroupItems[firstGroupItems.length - 1];
await page.click("button[title='Save']"); const startBound = firstActivity.start;
await page.click("li[title='Save and Finish Editing']"); const endBound = lastActivity.end;
const startBound = testPlan.TEST_GROUP[0].start;
const endBound = testPlan.TEST_GROUP[testPlan.TEST_GROUP.length - 1].end;
await page.goto(timelist.url);
// Switch to fixed time mode with all plan events within the bounds // Switch to fixed time mode with all plan events within the bounds
await page.goto( await page.goto(
@ -106,13 +80,14 @@ test.describe('Time List', () => {
); );
// Verify all events are displayed // Verify all events are displayed
const eventCount = await page.locator('.js-list-item').count(); const eventCount = await page.getByRole('row').count();
expect(eventCount).toEqual(testPlan.TEST_GROUP.length); // subtracting one for the header
await expect(eventCount - 1).toEqual(firstGroupItems.length);
}); });
await test.step('Does not show milliseconds in times', async () => { await test.step('Does not show milliseconds in times', async () => {
// Get the first activity // Get an activity
const row = page.locator('.js-list-item').first(); const row = page.getByRole('row').nth(2);
// Verify that none fo the times have milliseconds displayed. // Verify that none fo the times have milliseconds displayed.
// Example: 2024-11-17T16:00:00Z is correct and 2024-11-17T16:00:00.000Z is wrong // Example: 2024-11-17T16:00:00Z is correct and 2024-11-17T16:00:00.000Z is wrong
@ -120,5 +95,243 @@ test.describe('Time List', () => {
await expect(row.locator('.--end')).not.toContainText('.'); await expect(row.locator('.--end')).not.toContainText('.');
await expect(row.locator('.--duration')).not.toContainText('.'); await expect(row.locator('.--duration')).not.toContainText('.');
}); });
await test.step('Shows activity properties when a row is selected', async () => {
await page.getByRole('row').nth(2).click();
// Find the activity state section in the inspector
await page.getByRole('tab', { name: 'Activity' }).click();
// Check that activity state label is displayed in the inspector.
await expect(page.getByLabel('Activity Status').locator("[aria-selected='true']")).toHaveText(
'Not started'
);
});
}); });
}); });
test("View a timelist in expanded view, verify all the activities are displayed and selecting an activity shows it's properties", async ({
page
}) => {
// Goto baseURL
await page.goto('./', { waitUntil: 'domcontentloaded' });
const timelist = await test.step('Create a Time List', async () => {
const createdTimeList = await createDomainObjectWithDefaults(page, { type: 'Time List' });
const objectName = await page.locator('.l-browse-bar__object-name').innerText();
expect(objectName).toBe(createdTimeList.name);
return createdTimeList;
});
await test.step('Create a Plan and add it to the timelist', async () => {
await createPlanFromJSON(page, {
name: 'Test Plan',
json: examplePlanSmall1,
parent: timelist.uuid
});
// Ensure that all activities are shown in the expanded view
const groups = Object.keys(examplePlanSmall1);
const firstGroupKey = groups[0];
const firstGroupItems = examplePlanSmall1[firstGroupKey];
const firstActivity = firstGroupItems[0];
const lastActivity = firstGroupItems[firstGroupItems.length - 1];
const startBound = firstActivity.start;
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`
);
// Change the object to edit mode
await page.getByRole('button', { name: 'Edit Object' }).click();
// Find the display properties section in the inspector
await page.getByRole('tab', { name: 'View Properties' }).click();
// Switch to expanded view and save the setting
await page.getByLabel('Display Style').selectOption({ label: 'Expanded' });
// Click on the "Save" button
await page.getByRole('button', { name: 'Save' }).click();
await page.getByRole('listitem', { name: 'Save and Finish Editing' }).click();
// Verify all events are displayed
const eventCount = await page.getByRole('row').count();
await expect(eventCount).toEqual(firstGroupItems.length);
});
await test.step('Shows activity properties when a row is selected', async () => {
await page.getByRole('row').nth(2).click();
// Find the activity state section in the inspector
await page.getByRole('tab', { name: 'Activity' }).click();
// Check that activity state label is displayed in the inspector.
await expect(page.getByLabel('Activity Status').locator("[aria-selected='true']")).toHaveText(
'Not started'
);
});
});
/**
* The regular expression used to parse the countdown string.
* Some examples of valid Countdown strings:
* ```
* '35D 02:03:04'
* '-1D 01:02:03'
* '01:02:03'
* '-05:06:07'
* ```
*/
const COUNTDOWN_REGEXP = /(-)?(\d+D\s)?(\d{2}):(\d{2}):(\d{2})/;
/**
* @typedef {Object} CountdownOrUpObject
* @property {string} sign - The sign of the countdown ('-' if the countdown is negative, '+' otherwise).
* @property {string} days - The number of days in the countdown (undefined if there are no days).
* @property {string} hours - The number of hours in the countdown.
* @property {string} minutes - The number of minutes in the countdown.
* @property {string} seconds - The number of seconds in the countdown.
* @property {string} toString - The countdown string.
*/
/**
* Object representing the indices of the capture groups in a countdown regex match.
*
* @typedef {{ SIGN: number, DAYS: number, HOURS: number, MINUTES: number, SECONDS: number, REGEXP: RegExp }}
* @property {number} SIGN - The index for the sign capture group (1 if a '-' sign is present, otherwise undefined).
* @property {number} DAYS - The index for the days capture group (2 for the number of days, otherwise undefined).
* @property {number} HOURS - The index for the hours capture group (3 for the hour part of the time).
* @property {number} MINUTES - The index for the minutes capture group (4 for the minute part of the time).
* @property {number} SECONDS - The index for the seconds capture group (5 for the second part of the time).
*/
const COUNTDOWN = Object.freeze({
SIGN: 1,
DAYS: 2,
HOURS: 3,
MINUTES: 4,
SECONDS: 5
});
test.describe('Time List with controlled clock', () => {
test.use({
clockOptions: {
now: getEarliestStartTime(examplePlanSmall3),
shouldAdvanceTime: true
}
});
test.beforeEach(async ({ page }) => {
await page.goto('./', { waitUntil: 'domcontentloaded' });
});
test('Time List shows current events and counts down correctly in real-time mode', async ({
page
}) => {
await test.step('Create a Time List, add a Plan to it, and switch to real-time mode', async () => {
// Create Time List
const timelist = await createDomainObjectWithDefaults(page, {
type: 'Time List'
});
// Create a Plan with events that count down and up.
// Add it as a child to the Time List.
await createPlanFromJSON(page, {
json: examplePlanSmall3,
parent: timelist.uuid
});
// 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`
);
});
const countUpCells = [
getCellByIndex(page, 1, TIME_TO_FROM_COLUMN),
getCellByIndex(page, 2, TIME_TO_FROM_COLUMN)
];
const countdownCells = [
getCellByIndex(page, 3, TIME_TO_FROM_COLUMN),
getCellByIndex(page, 4, TIME_TO_FROM_COLUMN)
];
// Verify that the countdown cells are counting down
for (let i = 0; i < countdownCells.length; i++) {
await test.step(`Countdown cell ${i + 1} counts down`, async () => {
const countdownCell = countdownCells[i];
// Get the initial countdown timestamp object
const beforeCountdown = await getAndAssertCountdownOrUpObject(page, i + 3);
// should not have a '-' sign
await expect(countdownCell).not.toHaveText('-');
// Wait until it changes
await expect(countdownCell).not.toHaveText(beforeCountdown.toString());
// Get the new countdown timestamp object
const afterCountdown = await getAndAssertCountdownOrUpObject(page, i + 3);
// Verify that the new countdown timestamp object is less than the old one
expect(Number(afterCountdown.seconds)).toBeLessThan(Number(beforeCountdown.seconds));
});
}
// Verify that the count-up cells are counting up
for (let i = 0; i < countUpCells.length; i++) {
await test.step(`Count-up cell ${i + 1} counts up`, async () => {
const countUpCell = countUpCells[i];
// Get the initial count-up timestamp object
const beforeCountUp = await getAndAssertCountdownOrUpObject(page, i + 1);
// should not have a '+' sign
await expect(countUpCell).not.toHaveText('+');
// Wait until it changes
await expect(countUpCell).not.toHaveText(beforeCountUp.toString());
// Get the new count-up timestamp object
const afterCountUp = await getAndAssertCountdownOrUpObject(page, i + 1);
// Verify that the new count-up timestamp object is greater than the old one
expect(Number(afterCountUp.seconds)).toBeGreaterThan(Number(beforeCountUp.seconds));
});
}
});
});
/**
* Get the cell at the given row and column indices.
* @param {import('@playwright/test').Page} page
* @param {number} rowIndex
* @param {number} columnIndex
* @returns {import('@playwright/test').Locator} cell
*/
function getCellByIndex(page, rowIndex, columnIndex) {
return page.getByRole('cell').nth(rowIndex * NUM_COLUMNS + columnIndex);
}
/**
* Return the innerText of the cell at the given row and column indices.
* @param {import('@playwright/test').Page} page
* @param {number} rowIndex
* @param {number} columnIndex
* @returns {Promise<string>} text
*/
async function getCellTextByIndex(page, rowIndex, columnIndex) {
const text = await getCellByIndex(page, rowIndex, columnIndex).innerText();
return text;
}
/**
* Get the text from the countdown (or countup) cell in the given row, assert that it matches the countdown/countup
* regex, and return an object representing the countdown.
* @param {import('@playwright/test').Page} page
* @param {number} rowIndex the row index
* @returns {Promise<CountdownOrUpObject>} The countdown (or countup) object
*/
async function getAndAssertCountdownOrUpObject(page, rowIndex) {
const timeToFrom = await getCellTextByIndex(page, HEADER_ROW + rowIndex, TIME_TO_FROM_COLUMN);
expect(timeToFrom).toMatch(COUNTDOWN_REGEXP);
const match = timeToFrom.match(COUNTDOWN_REGEXP);
return {
sign: match[COUNTDOWN.SIGN],
days: match[COUNTDOWN.DAYS],
hours: match[COUNTDOWN.HOURS],
minutes: match[COUNTDOWN.MINUTES],
seconds: match[COUNTDOWN.SECONDS],
toString: () => timeToFrom
};
}

View File

@ -1,5 +1,5 @@
/***************************************************************************** /*****************************************************************************
* Open MCT, Copyright (c) 2014-2023, United States Government * Open MCT, Copyright (c) 2014-2024, United States Government
* as represented by the Administrator of the National Aeronautics and Space * as represented by the Administrator of the National Aeronautics and Space
* Administration. All rights reserved. * Administration. All rights reserved.
* *
@ -20,12 +20,12 @@
* at runtime from the About dialog for additional information. * at runtime from the About dialog for additional information.
*****************************************************************************/ *****************************************************************************/
const { test, expect } = require('../../../pluginFixtures'); import {
const {
createDomainObjectWithDefaults, createDomainObjectWithDefaults,
createPlanFromJSON, createPlanFromJSON,
setIndependentTimeConductorBounds setIndependentTimeConductorBounds
} = require('../../../appActions'); } from '../../../appActions.js';
import { expect, test } from '../../../pluginFixtures.js';
const testPlan = { const testPlan = {
TEST_GROUP: [ TEST_GROUP: [

View File

@ -1,5 +1,5 @@
/***************************************************************************** /*****************************************************************************
* Open MCT, Copyright (c) 2014-2023, United States Government * Open MCT, Copyright (c) 2014-2024, United States Government
* as represented by the Administrator of the National Aeronautics and Space * as represented by the Administrator of the National Aeronautics and Space
* Administration. All rights reserved. * Administration. All rights reserved.
* *
@ -24,7 +24,7 @@
This test suite is dedicated to tests which verify the basic operations surrounding Clock. This test suite is dedicated to tests which verify the basic operations surrounding Clock.
*/ */
const { test, expect } = require('../../../../baseFixtures'); import { expect, test } from '../../../../baseFixtures.js';
test.describe('Clock Generator CRUD Operations', () => { test.describe('Clock Generator CRUD Operations', () => {
test('Timezone dropdown will collapse when clicked outside or on dropdown icon again', async ({ test('Timezone dropdown will collapse when clicked outside or on dropdown icon again', async ({
@ -38,7 +38,7 @@ test.describe('Clock Generator CRUD Operations', () => {
await page.goto('./', { waitUntil: 'domcontentloaded' }); await page.goto('./', { waitUntil: 'domcontentloaded' });
//Click the Create button //Click the Create button
await page.click('button:has-text("Create")'); await page.getByRole('button', { name: 'Create' }).click();
// Click Clock // Click Clock
await page.getByRole('menuitem').first().click(); await page.getByRole('menuitem').first().click();

View File

@ -1,5 +1,5 @@
/***************************************************************************** /*****************************************************************************
* Open MCT, Copyright (c) 2014-2023, United States Government * Open MCT, Copyright (c) 2014-2024, United States Government
* as represented by the Administrator of the National Aeronautics and Space * as represented by the Administrator of the National Aeronautics and Space
* Administration. All rights reserved. * Administration. All rights reserved.
* *
@ -22,7 +22,7 @@
// FIXME: Remove this eslint exception once tests are implemented // FIXME: Remove this eslint exception once tests are implemented
// eslint-disable-next-line no-unused-vars // eslint-disable-next-line no-unused-vars
const { test, expect } = require('../../../../baseFixtures'); import { expect, test } from '../../../../baseFixtures.js';
test.describe('Remote Clock', () => { test.describe('Remote Clock', () => {
// eslint-disable-next-line require-await // eslint-disable-next-line require-await

View File

@ -1,5 +1,5 @@
/***************************************************************************** /*****************************************************************************
* Open MCT, Copyright (c) 2014-2023, United States Government * Open MCT, Copyright (c) 2014-2024, United States Government
* as represented by the Administrator of the National Aeronautics and Space * as represented by the Administrator of the National Aeronautics and Space
* Administration. All rights reserved. * Administration. All rights reserved.
* *
@ -19,30 +19,29 @@
* this source code distribution or the Licensing information page available * this source code distribution or the Licensing information page available
* at runtime from the About dialog for additional information. * at runtime from the About dialog for additional information.
*****************************************************************************/ *****************************************************************************/
/* global __dirname */
/* /*
This test suite is dedicated to tests which verify the basic operations surrounding conditionSets. Note: this 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 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. demonstrate some playwright for test developers. This pattern should not be re-used in other CRUD suites.
*/ */
const { test, expect } = require('../../../../pluginFixtures.js'); import { fileURLToPath } from 'url';
const {
import {
createDomainObjectWithDefaults, createDomainObjectWithDefaults,
createExampleTelemetryObject createExampleTelemetryObject
} = require('../../../../appActions'); } from '../../../../appActions.js';
const path = require('path'); import { expect, test } from '../../../../pluginFixtures.js';
let conditionSetUrl; let conditionSetUrl;
let getConditionSetIdentifierFromUrl;
test.describe.serial('Condition Set CRUD Operations on @localStorage', () => { test.describe.serial('Condition Set CRUD Operations on @localStorage @2p', () => {
test.beforeAll(async ({ browser }) => { test.beforeAll(async ({ browser }) => {
//TODO: This needs to be refactored //TODO: This needs to be refactored
const context = await browser.newContext(); const context = await browser.newContext();
const page = await context.newPage(); const page = await context.newPage();
await page.goto('./', { waitUntil: 'domcontentloaded' }); await page.goto('./', { waitUntil: 'domcontentloaded' });
await page.click('button:has-text("Create")'); await page.getByRole('button', { name: 'Create' }).click();
await page.locator('li[role="menuitem"]:has-text("Condition Set")').click(); await page.locator('li[role="menuitem"]:has-text("Condition Set")').click();
@ -50,47 +49,54 @@ test.describe.serial('Condition Set CRUD Operations on @localStorage', () => {
//Save localStorage for future test execution //Save localStorage for future test execution
await context.storageState({ await context.storageState({
path: path.resolve(__dirname, '../../../../test-data/recycled_local_storage.json') path: fileURLToPath(
new URL('../../../../test-data/recycled_local_storage.json', import.meta.url)
)
}); });
//Set object identifier from url //Set object identifier from url
conditionSetUrl = page.url(); conditionSetUrl = page.url();
getConditionSetIdentifierFromUrl = conditionSetUrl.split('/').pop().split('?')[0];
console.debug(`getConditionSetIdentifierFromUrl: ${getConditionSetIdentifierFromUrl}`);
await page.close(); await page.close();
}); });
//Load localStorage for subsequent tests //Load localStorage for subsequent tests
test.use({ test.use({
storageState: path.resolve(__dirname, '../../../../test-data/recycled_local_storage.json') storageState: fileURLToPath(
new URL('../../../../test-data/recycled_local_storage.json', import.meta.url)
)
}); });
//Begin suite of tests again localStorage //Begin suite of tests again localStorage
test('Condition set object properties persist in main view and inspector @localStorage', async ({ test.fixme(
page 'Condition set object properties persist in main view and inspector @localStorage',
}) => { async ({ page }) => {
//Navigate to baseURL with injected localStorage test.info().annotations.push({
await page.goto(conditionSetUrl, { waitUntil: 'networkidle' }); type: 'issue',
description: 'https://github.com/nasa/openmct/issues/7421'
});
//Navigate to baseURL with injected localStorage
await page.goto(conditionSetUrl, { waitUntil: 'networkidle' });
//Assertions on loaded Condition Set in main view. This is a stateful transition step after page.goto() //Assertions on loaded Condition Set in main view. This is a stateful transition step after page.goto()
await expect await expect
.soft(page.locator('.l-browse-bar__object-name')) .soft(page.locator('.l-browse-bar__object-name'))
.toContainText('Unnamed Condition Set'); .toContainText('Unnamed Condition Set');
//Assertions on loaded Condition Set in Inspector //Assertions on loaded Condition Set in Inspector
expect.soft(page.locator('_vue=item.name=Unnamed Condition Set')).toBeTruthy(); expect.soft(page.locator('_vue=item.name=Unnamed Condition Set')).toBeTruthy();
//Reload Page //Reload Page
await Promise.all([page.reload(), page.waitForLoadState('networkidle')]); await Promise.all([page.reload(), page.waitForLoadState('networkidle')]);
//Re-verify after reload //Re-verify after reload
await expect await expect
.soft(page.locator('.l-browse-bar__object-name')) .soft(page.locator('.l-browse-bar__object-name'))
.toContainText('Unnamed Condition Set'); .toContainText('Unnamed Condition Set');
//Assertions on loaded Condition Set in Inspector //Assertions on loaded Condition Set in Inspector
expect.soft(page.locator('_vue=item.name=Unnamed Condition Set')).toBeTruthy(); expect.soft(page.locator('_vue=item.name=Unnamed Condition Set')).toBeTruthy();
}); }
);
test('condition set object can be modified on @localStorage', async ({ page, openmctConfig }) => { test('condition set object can be modified on @localStorage', async ({ page, openmctConfig }) => {
const { myItemsFolderName } = openmctConfig; const { myItemsFolderName } = openmctConfig;
@ -192,7 +198,7 @@ test.describe.serial('Condition Set CRUD Operations on @localStorage', () => {
.first() .first()
.click(); .click();
// Click hamburger button // Click hamburger button
await page.locator('[title="More options"]').click(); await page.locator('[title="More actions"]').click();
// Click 'Remove' and press OK // Click 'Remove' and press OK
await page.locator('li[role="menuitem"]:has-text("Remove")').click(); await page.locator('li[role="menuitem"]:has-text("Remove")').click();
@ -230,7 +236,7 @@ test.describe('Basic Condition Set Use', () => {
await page.goto(conditionSet.url); await page.goto(conditionSet.url);
// Change the object to edit mode // Change the object to edit mode
await page.locator('[title="Edit"]').click(); await page.getByLabel('Edit Object').click();
// Click Add Condition button // Click Add Condition button
await page.locator('#addCondition').click(); await page.locator('#addCondition').click();
@ -258,7 +264,7 @@ test.describe('Basic Condition Set Use', () => {
await page.goto(conditionSet.url); await page.goto(conditionSet.url);
// Change the object to edit mode // Change the object to edit mode
await page.locator('[title="Edit"]').click(); await page.getByLabel('Edit Object').click();
// Expand the 'My Items' folder in the left tree // Expand the 'My Items' folder in the left tree
page.click('button[title="Show selected item in tree"]'); page.click('button[title="Show selected item in tree"]');
@ -295,7 +301,7 @@ test.describe('Basic Condition Set Use', () => {
await page.getByTitle('Show selected item in tree').click(); await page.getByTitle('Show selected item in tree').click();
await page.goto(conditionSet.url); await page.goto(conditionSet.url);
// Change the object to edit mode // Change the object to edit mode
await page.locator('[title="Edit"]').click(); await page.getByLabel('Edit Object').click();
// Create two conditions // Create two conditions
await page.locator('#addCondition').click(); await page.locator('#addCondition').click();
@ -362,7 +368,7 @@ test.describe('Basic Condition Set Use', () => {
// Edit SWG to add 8 second loading delay to simulate the case // Edit SWG to add 8 second loading delay to simulate the case
// where telemetry is not available. // where telemetry is not available.
await page.getByTitle('More options').click(); await page.getByTitle('More actions').click();
await page.getByRole('menuitem', { name: 'Edit Properties...' }).click(); await page.getByRole('menuitem', { name: 'Edit Properties...' }).click();
await page.getByRole('spinbutton', { name: 'Loading Delay (ms)' }).fill('8000'); await page.getByRole('spinbutton', { name: 'Loading Delay (ms)' }).fill('8000');
await page.getByLabel('Save').click(); await page.getByLabel('Save').click();

View File

@ -1,5 +1,5 @@
/***************************************************************************** /*****************************************************************************
* Open MCT, Copyright (c) 2014-2023, United States Government * Open MCT, Copyright (c) 2014-2024, United States Government
* as represented by the Administrator of the National Aeronautics and Space * as represented by the Administrator of the National Aeronautics and Space
* Administration. All rights reserved. * Administration. All rights reserved.
* *
@ -19,20 +19,19 @@
* this source code distribution or the Licensing information page available * this source code distribution or the Licensing information page available
* at runtime from the About dialog for additional information. * at runtime from the About dialog for additional information.
*****************************************************************************/ *****************************************************************************/
/* global __dirname */ import { fileURLToPath } from 'url';
const { test, expect } = require('../../../../pluginFixtures');
const path = require('path');
const {
createDomainObjectWithDefaults,
setStartOffset,
setFixedTimeMode,
setRealTimeMode,
setIndependentTimeConductorBounds
} = require('../../../../appActions');
const LOCALSTORAGE_PATH = path.resolve( import {
__dirname, createDomainObjectWithDefaults,
'../../../../test-data/display_layout_with_child_layouts.json' setFixedTimeMode,
setIndependentTimeConductorBounds,
setRealTimeMode,
setStartOffset
} from '../../../../appActions.js';
import { expect, test } from '../../../../pluginFixtures.js';
const LOCALSTORAGE_PATH = fileURLToPath(
new URL('../../../../test-data/display_layout_with_child_layouts.json', import.meta.url)
); );
const TINY_IMAGE_BASE64 = const TINY_IMAGE_BASE64 =
'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAgAAAAIAQMAAAD+wSzIAAAABlBMVEX///+/v7+jQ3Y5AAAADklEQVQI12P4AIX8EAgALgAD/aNpbtEAAAAASUVORK5CYII'; 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAgAAAAIAQMAAAD+wSzIAAAABlBMVEX///+/v7+jQ3Y5AAAADklEQVQI12P4AIX8EAgALgAD/aNpbtEAAAAASUVORK5CYII';
@ -48,10 +47,10 @@ test.describe('Display Layout Toolbar Actions @localStorage', () => {
.filter({ hasText: 'Parent Display Layout Display Layout' }) .filter({ hasText: 'Parent Display Layout Display Layout' })
.first() .first()
.click(); .click();
await page.getByLabel('Edit').click(); await page.getByLabel('Edit Object').click();
}); });
test.use({ test.use({
storageState: path.resolve(__dirname, LOCALSTORAGE_PATH) storageState: LOCALSTORAGE_PATH
}); });
test('can add/remove Text element to a single layout', async ({ page }) => { test('can add/remove Text element to a single layout', async ({ page }) => {
@ -134,7 +133,7 @@ test.describe('Display Layout', () => {
name: 'Test Display Layout' name: 'Test Display Layout'
}); });
// Edit Display Layout // Edit Display Layout
await page.locator('[title="Edit"]').click(); await page.getByLabel('Edit Object').click();
// Expand the 'My Items' folder in the left tree // Expand the 'My Items' folder in the left tree
await page.locator('.c-tree__item__view-control.c-disclosure-triangle').click(); await page.locator('.c-tree__item__view-control.c-disclosure-triangle').click();
@ -162,6 +161,13 @@ test.describe('Display Layout', () => {
const trimmedDisplayValue = displayLayoutValue.trim(); const trimmedDisplayValue = displayLayoutValue.trim();
expect(trimmedDisplayValue).toBe(formattedTelemetryValue); expect(trimmedDisplayValue).toBe(formattedTelemetryValue);
// ensure we can right click on the alpha-numeric widget and view historical data
await page.getByLabel('Sine', { exact: true }).click({
button: 'right'
});
await page.getByLabel('View Historical Data').click();
await expect(page.getByLabel('Plot Container Style Target')).toBeVisible();
}); });
test('alpha-numeric widget telemetry value exactly matches latest telemetry value received in fixed time', async ({ test('alpha-numeric widget telemetry value exactly matches latest telemetry value received in fixed time', async ({
page page
@ -172,7 +178,7 @@ test.describe('Display Layout', () => {
name: 'Test Display Layout' name: 'Test Display Layout'
}); });
// Edit Display Layout // Edit Display Layout
await page.locator('[title="Edit"]').click(); await page.getByLabel('Edit Object').click();
// Expand the 'My Items' folder in the left tree // Expand the 'My Items' folder in the left tree
await page.locator('.c-tree__item__view-control.c-disclosure-triangle').click(); await page.locator('.c-tree__item__view-control.c-disclosure-triangle').click();
@ -214,7 +220,7 @@ test.describe('Display Layout', () => {
name: 'Test Display Layout' name: 'Test Display Layout'
}); });
// Edit Display Layout // Edit Display Layout
await page.locator('[title="Edit"]').click(); await page.getByLabel('Edit Object').click();
// Expand the 'My Items' folder in the left tree // Expand the 'My Items' folder in the left tree
await page.locator('.c-tree__item__view-control.c-disclosure-triangle').click(); await page.locator('.c-tree__item__view-control.c-disclosure-triangle').click();
@ -256,7 +262,7 @@ test.describe('Display Layout', () => {
type: 'Display Layout' type: 'Display Layout'
}); });
// Edit Display Layout // Edit Display Layout
await page.locator('[title="Edit"]').click(); await page.getByLabel('Edit Object').click();
// Expand the 'My Items' folder in the left tree // Expand the 'My Items' folder in the left tree
await page.locator('.c-tree__item__view-control.c-disclosure-triangle').click(); await page.locator('.c-tree__item__view-control.c-disclosure-triangle').click();
@ -302,7 +308,7 @@ test.describe('Display Layout', () => {
type: 'Display Layout' type: 'Display Layout'
}); });
// Edit Display Layout // Edit Display Layout
await page.locator('[title="Edit"]').click(); await page.getByLabel('Edit Object').click();
// Expand the 'My Items' folder in the left tree // Expand the 'My Items' folder in the left tree
await page.locator('.c-tree__item__view-control.c-disclosure-triangle').click(); await page.locator('.c-tree__item__view-control.c-disclosure-triangle').click();
@ -358,7 +364,7 @@ test.describe('Display Layout', () => {
name: 'Test Display Layout' name: 'Test Display Layout'
}); });
// Edit Display Layout // Edit Display Layout
await page.locator('[title="Edit"]').click(); await page.getByLabel('Edit Object').click();
// Expand the 'My Items' folder in the left tree // Expand the 'My Items' folder in the left tree
await page.locator('.c-tree__item__view-control.c-disclosure-triangle').click(); await page.locator('.c-tree__item__view-control.c-disclosure-triangle').click();
@ -450,7 +456,7 @@ async function removeLayoutObject(page, layoutObject) {
// eslint-disable-next-line playwright/no-force-option // eslint-disable-next-line playwright/no-force-option
.click({ force: true }); .click({ force: true });
await page.getByTitle('Delete the selected object').click(); await page.getByTitle('Delete the selected object').click();
await page.getByRole('button', { name: 'OK' }).click(); await page.getByRole('button', { name: 'OK', exact: true }).click();
} }
/** /**

View File

@ -1,5 +1,5 @@
/***************************************************************************** /*****************************************************************************
* Open MCT, Copyright (c) 2014-2023, United States Government * Open MCT, Copyright (c) 2014-2024, United States Government
* as represented by the Administrator of the National Aeronautics and Space * as represented by the Administrator of the National Aeronautics and Space
* Administration. All rights reserved. * Administration. All rights reserved.
* *
@ -20,8 +20,8 @@
* at runtime from the About dialog for additional information. * at runtime from the About dialog for additional information.
*****************************************************************************/ *****************************************************************************/
const { test, expect } = require('../../../../pluginFixtures'); import * as utils from '../../../../helper/faultUtils.js';
const utils = require('../../../../helper/faultUtils'); import { expect, test } from '../../../../pluginFixtures.js';
test.describe('The Fault Management Plugin using example faults', () => { test.describe('The Fault Management Plugin using example faults', () => {
test.beforeEach(async ({ page }) => { test.beforeEach(async ({ page }) => {

View File

@ -1,5 +1,5 @@
/***************************************************************************** /*****************************************************************************
* Open MCT, Copyright (c) 2014-2023, United States Government * Open MCT, Copyright (c) 2014-2024, United States Government
* as represented by the Administrator of the National Aeronautics and Space * as represented by the Administrator of the National Aeronautics and Space
* Administration. All rights reserved. * Administration. All rights reserved.
* *
@ -19,18 +19,17 @@
* this source code distribution or the Licensing information page available * this source code distribution or the Licensing information page available
* at runtime from the About dialog for additional information. * at runtime from the About dialog for additional information.
*****************************************************************************/ *****************************************************************************/
/* global __dirname */
const { test, expect } = require('../../../../pluginFixtures'); import { fileURLToPath } from 'url';
const {
import {
createDomainObjectWithDefaults, createDomainObjectWithDefaults,
setIndependentTimeConductorBounds setIndependentTimeConductorBounds
} = require('../../../../appActions'); } from '../../../../appActions.js';
const path = require('path'); import { expect, test } from '../../../../pluginFixtures.js';
const LOCALSTORAGE_PATH = path.resolve( const LOCALSTORAGE_PATH = fileURLToPath(
__dirname, new URL('../../../../test-data/flexible_layout_with_child_layouts.json', import.meta.url)
'../../../../test-data/flexible_layout_with_child_layouts.json'
); );
test.describe('Flexible Layout', () => { test.describe('Flexible Layout', () => {
@ -74,7 +73,7 @@ test.describe('Flexible Layout', () => {
}) => { }) => {
await page.goto(flexibleLayout.url); await page.goto(flexibleLayout.url);
// Edit Flexible Layout // Edit Flexible Layout
await page.locator('[title="Edit"]').click(); await page.getByLabel('Edit Object').click();
// Expand the 'My Items' folder in the left tree // Expand the 'My Items' folder in the left tree
await page.locator('.c-tree__item__view-control.c-disclosure-triangle').first().click(); await page.locator('.c-tree__item__view-control.c-disclosure-triangle').first().click();
@ -167,7 +166,7 @@ test.describe('Flexible Layout', () => {
}) => { }) => {
await page.goto(flexibleLayout.url); await page.goto(flexibleLayout.url);
// Edit Flexible Layout // Edit Flexible Layout
await page.locator('[title="Edit"]').click(); await page.getByLabel('Edit Object').click();
// Expand the 'My Items' folder in the left tree // Expand the 'My Items' folder in the left tree
await page.locator('.c-tree__item__view-control.c-disclosure-triangle').first().click(); await page.locator('.c-tree__item__view-control.c-disclosure-triangle').first().click();
@ -198,7 +197,7 @@ test.describe('Flexible Layout', () => {
}); });
await page.goto(flexibleLayout.url); await page.goto(flexibleLayout.url);
// Edit Flexible Layout // Edit Flexible Layout
await page.locator('[title="Edit"]').click(); await page.getByLabel('Edit Object').click();
// Expand the 'My Items' folder in the left tree // Expand the 'My Items' folder in the left tree
await page.locator('.c-tree__item__view-control.c-disclosure-triangle').click(); await page.locator('.c-tree__item__view-control.c-disclosure-triangle').click();
@ -235,7 +234,7 @@ test.describe('Flexible Layout', () => {
await page.goto(flexibleLayout.url); await page.goto(flexibleLayout.url);
// Edit Flexible Layout // Edit Flexible Layout
await page.locator('[title="Edit"]').click(); await page.getByLabel('Edit Object').click();
// Expand the 'My Items' folder in the left tree // Expand the 'My Items' folder in the left tree
await page.locator('.c-tree__item__view-control.c-disclosure-triangle').click(); await page.locator('.c-tree__item__view-control.c-disclosure-triangle').click();
@ -267,7 +266,7 @@ test.describe('Flexible Layout', () => {
test.describe('Flexible Layout Toolbar Actions @localStorage', () => { test.describe('Flexible Layout Toolbar Actions @localStorage', () => {
test.use({ test.use({
storageState: path.resolve(__dirname, LOCALSTORAGE_PATH) storageState: LOCALSTORAGE_PATH
}); });
test.beforeEach(async ({ page }) => { test.beforeEach(async ({ page }) => {
@ -277,40 +276,43 @@ test.describe('Flexible Layout Toolbar Actions @localStorage', () => {
.filter({ hasText: 'Parent Flexible Layout Flexible Layout' }) .filter({ hasText: 'Parent Flexible Layout Flexible Layout' })
.first() .first()
.click(); .click();
await page.getByLabel('Edit').click(); await page.getByLabel('Edit Object').click();
}); });
test('Add/Remove Container', async ({ page }) => { test('Add/Remove Container', async ({ page }) => {
test.info().annotations.push({ test.info().annotations.push({
type: 'issue', type: 'issue',
description: 'https://github.com/nasa/openmct/issues/7234' description: 'https://github.com/nasa/openmct/issues/7234'
}); });
expect(await page.getByRole('group', { name: 'Container' }).count()).toEqual(2);
await page.getByRole('group', { name: 'Container' }).nth(1).click(); const containerHandles = page.getByRole('columnheader', { name: 'Handle' });
expect(await containerHandles.count()).toEqual(2);
await page.getByRole('columnheader', { name: 'Container Handle 1' }).click();
await page.getByTitle('Add Container').click(); await page.getByTitle('Add Container').click();
expect(await page.getByRole('group', { name: 'Container' }).count()).toEqual(3); expect(await containerHandles.count()).toEqual(3);
await page.getByTitle('Remove Container').click(); await page.getByTitle('Remove Container').click();
await expect(page.getByRole('dialog')).toHaveText( await expect(page.getByRole('dialog', { name: 'Overlay' })).toHaveText(
'This action will permanently delete this container from this Flexible Layout. Do you want to continue?' 'This action will permanently delete this container from this Flexible Layout. Do you want to continue?'
); );
await page.getByRole('button', { name: 'OK' }).click(); await page.getByRole('button', { name: 'OK', exact: true }).click();
expect(await page.getByRole('group', { name: 'Container' }).count()).toEqual(2); expect(await containerHandles.count()).toEqual(2);
}); });
test('Remove Frame', async ({ page }) => { test('Remove Frame', async ({ page }) => {
expect(await page.getByRole('group', { name: 'Frame' }).count()).toEqual(2); expect(await page.getByRole('group', { name: 'Frame' }).count()).toEqual(2);
await page.getByRole('group', { name: 'Child Layout 1' }).click(); await page.getByRole('group', { name: 'Child Layout 1' }).click();
await page.getByTitle('Remove Frame').click(); await page.getByTitle('Remove Frame').click();
await expect(page.getByRole('dialog')).toHaveText( await expect(page.getByRole('dialog', { name: 'Overlay' })).toHaveText(
'This action will remove this frame from this Flexible Layout. Do you want to continue?' 'This action will remove this frame from this Flexible Layout. Do you want to continue?'
); );
await page.getByRole('button', { name: 'OK' }).click(); await page.getByRole('button', { name: 'OK', exact: true }).click();
expect(await page.getByRole('group', { name: 'Frame' }).count()).toEqual(1); expect(await page.getByRole('group', { name: 'Frame' }).count()).toEqual(1);
}); });
test('Columns/Rows Layout Toggle', async ({ page }) => { test('Columns/Rows Layout Toggle', async ({ page }) => {
await page.getByRole('group', { name: 'Container' }).nth(1).click(); await page.getByRole('columnheader', { name: 'Container Handle 1' }).click();
expect(await page.locator('.c-fl--rows').count()).toEqual(0); const flexRows = page.getByLabel('Flexible Layout Row');
expect(await flexRows.count()).toEqual(0);
await page.getByTitle('Columns layout').click(); await page.getByTitle('Columns layout').click();
expect(await page.locator('.c-fl--rows').count()).toEqual(1); expect(await flexRows.count()).toEqual(1);
await page.getByTitle('Rows layout').click(); await page.getByTitle('Rows layout').click();
expect(await page.locator('.c-fl--rows').count()).toEqual(0); expect(await flexRows.count()).toEqual(0);
}); });
}); });

View File

@ -1,5 +1,5 @@
/***************************************************************************** /*****************************************************************************
* Open MCT, Copyright (c) 2014-2023, United States Government * Open MCT, Copyright (c) 2014-2024, United States Government
* as represented by the Administrator of the National Aeronautics and Space * as represented by the Administrator of the National Aeronautics and Space
* Administration. All rights reserved. * Administration. All rights reserved.
* *
@ -24,12 +24,13 @@
* This test suite is dedicated to testing the Gauge component. * This test suite is dedicated to testing the Gauge component.
*/ */
const { test, expect } = require('../../../../pluginFixtures'); import { v4 as uuid } from 'uuid';
const {
import {
createDomainObjectWithDefaults, createDomainObjectWithDefaults,
createExampleTelemetryObject createExampleTelemetryObject
} = require('../../../../appActions'); } from '../../../../appActions.js';
const uuid = require('uuid').v4; import { expect, test } from '../../../../pluginFixtures.js';
test.describe('Gauge', () => { test.describe('Gauge', () => {
test.beforeEach(async ({ page }) => { test.beforeEach(async ({ page }) => {
@ -40,8 +41,6 @@ test.describe('Gauge', () => {
test('Can add and remove telemetry sources @unstable', async ({ page }) => { test('Can add and remove telemetry sources @unstable', async ({ page }) => {
// Create the gauge with defaults // Create the gauge with defaults
const gauge = await createDomainObjectWithDefaults(page, { type: 'Gauge' }); const gauge = await createDomainObjectWithDefaults(page, { type: 'Gauge' });
const editButtonLocator = page.locator('button[title="Edit"]');
const saveButtonLocator = page.locator('button[title="Save"]');
// Create a sine wave generator within the gauge // Create a sine wave generator within the gauge
const swg1 = await createDomainObjectWithDefaults(page, { const swg1 = await createDomainObjectWithDefaults(page, {
@ -53,9 +52,9 @@ test.describe('Gauge', () => {
// Navigate to the gauge and verify that // Navigate to the gauge and verify that
// the SWG appears in the elements pool // the SWG appears in the elements pool
await page.goto(gauge.url); await page.goto(gauge.url);
await editButtonLocator.click(); await page.getByLabel('Edit Object').click();
await expect.soft(page.locator(`#inspector-elements-tree >> text=${swg1.name}`)).toBeVisible(); await expect.soft(page.locator(`#inspector-elements-tree >> text=${swg1.name}`)).toBeVisible();
await saveButtonLocator.click(); await page.getByRole('button', { name: 'Save' }).click();
await page.getByRole('listitem', { name: 'Save and Finish Editing' }).click(); await page.getByRole('listitem', { name: 'Save and Finish Editing' }).click();
// Create another sine wave generator within the gauge // Create another sine wave generator within the gauge
@ -78,10 +77,10 @@ test.describe('Gauge', () => {
// Navigate to the gauge and verify that the new SWG // Navigate to the gauge and verify that the new SWG
// appears in the elements pool and the old one is gone // appears in the elements pool and the old one is gone
await page.goto(gauge.url); await page.goto(gauge.url);
await editButtonLocator.click(); 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=${swg1.name}`)).toBeHidden();
await expect.soft(page.locator(`#inspector-elements-tree >> text=${swg2.name}`)).toBeVisible(); await expect.soft(page.locator(`#inspector-elements-tree >> text=${swg2.name}`)).toBeVisible();
await saveButtonLocator.click(); await page.getByRole('button', { name: 'Save' }).click();
// Right click on the new SWG in the elements pool and delete it // 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.locator(`#inspector-elements-tree >> text=${swg2.name}`).click({
@ -108,7 +107,7 @@ test.describe('Gauge', () => {
description: 'https://github.com/nasa/openmct/issues/5356' description: 'https://github.com/nasa/openmct/issues/5356'
}); });
//Click the Create button //Click the Create button
await page.click('button:has-text("Create")'); await page.getByRole('button', { name: 'Create' }).click();
// Click the object specified by 'type' // Click the object specified by 'type'
await page.click(`li[role='menuitem']:text("Gauge")`); await page.click(`li[role='menuitem']:text("Gauge")`);
@ -127,7 +126,7 @@ test.describe('Gauge', () => {
// Create the gauge with defaults // Create the gauge with defaults
await createDomainObjectWithDefaults(page, { type: 'Gauge' }); await createDomainObjectWithDefaults(page, { type: 'Gauge' });
await page.click('button[title="More options"]'); await page.click('button[title="More actions"]');
await page.click('li[role="menuitem"]:has-text("Edit Properties")'); await page.click('li[role="menuitem"]:has-text("Edit Properties")');
// FIXME: We need better selectors for these custom form controls // FIXME: We need better selectors for these custom form controls
const displayCurrentValueSwitch = page.locator('.c-toggle-switch__slider >> nth=0'); const displayCurrentValueSwitch = page.locator('.c-toggle-switch__slider >> nth=0');
@ -137,7 +136,11 @@ test.describe('Gauge', () => {
// TODO: Verify changes in the UI // TODO: Verify changes in the UI
}); });
test('Gauge does not display NaN when data not available', async ({ page }) => { 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'
});
// Create a Gauge // Create a Gauge
const gauge = await createDomainObjectWithDefaults(page, { const gauge = await createDomainObjectWithDefaults(page, {
type: 'Gauge' type: 'Gauge'
@ -147,7 +150,7 @@ test.describe('Gauge', () => {
const swgWith5sDelay = await createExampleTelemetryObject(page, gauge.uuid); const swgWith5sDelay = await createExampleTelemetryObject(page, gauge.uuid);
await page.goto(swgWith5sDelay.url); await page.goto(swgWith5sDelay.url);
await page.getByTitle('More options').click(); await page.getByTitle('More actions').click();
await page.getByRole('menuitem', { name: /Edit Properties.../ }).click(); await page.getByRole('menuitem', { name: /Edit Properties.../ }).click();
//Edit Example Telemetry Object to include 5s loading Delay //Edit Example Telemetry Object to include 5s loading Delay

View File

@ -1,5 +1,5 @@
/***************************************************************************** /*****************************************************************************
* Open MCT, Copyright (c) 2014-2023, United States Government * Open MCT, Copyright (c) 2014-2024, United States Government
* as represented by the Administrator of the National Aeronautics and Space * as represented by the Administrator of the National Aeronautics and Space
* Administration. All rights reserved. * Administration. All rights reserved.
* *
@ -25,9 +25,9 @@ This test suite is dedicated to tests which verify the basic operations surround
but only assume that example imagery is present. but only assume that example imagery is present.
*/ */
/* globals process */ /* globals process */
const { waitForAnimations } = require('../../../../baseFixtures'); import { createDomainObjectWithDefaults, setRealTimeMode } from '../../../../appActions.js';
const { test, expect } = require('../../../../pluginFixtures'); import { waitForAnimations } from '../../../../baseFixtures.js';
const { createDomainObjectWithDefaults, setRealTimeMode } = require('../../../../appActions'); import { expect, test } from '../../../../pluginFixtures.js';
const backgroundImageSelector = '.c-imagery__main-image__background-image'; const backgroundImageSelector = '.c-imagery__main-image__background-image';
const panHotkey = process.platform === 'linux' ? ['Shift', 'Alt'] : ['Alt']; const panHotkey = process.platform === 'linux' ? ['Shift', 'Alt'] : ['Alt'];
const tagHotkey = ['Shift', 'Alt']; const tagHotkey = ['Shift', 'Alt'];
@ -61,6 +61,31 @@ test.describe('Example Imagery Object', () => {
await expect(page.locator('.c-hud')).toBeHidden(); await expect(page.locator('.c-hud')).toBeHidden();
}); });
test('Can right click on image and open it in a new tab @2p', async ({ page, context }) => {
// try to right click on image
const backgroundImage = await page.locator(backgroundImageSelector);
await backgroundImage.click({
button: 'right',
// eslint-disable-next-line playwright/no-force-option
force: true
});
// expect context menu to appear
await expect(page.getByText('Save Image As')).toBeVisible();
await expect(page.getByText('Open Image in New Tab')).toBeVisible();
// click on open image in new tab
const pagePromise = context.waitForEvent('page');
await page.getByText('Open Image in New Tab').click();
// expect new tab to be in browser
const newPage = await pagePromise;
await newPage.waitForLoadState();
// expect new tab url to have jpg in it
await expect(newPage.url()).toContain('.jpg');
});
// this requires CORS to be enabled in some fashion
test.fixme('Can right click on image and save it as a file', async ({ page }) => {});
test('Can adjust image brightness/contrast by dragging the sliders', async ({ test('Can adjust image brightness/contrast by dragging the sliders', async ({
page, page,
browserName browserName
@ -338,7 +363,7 @@ test.describe('Example Imagery in Display Layout', () => {
await page.locator('li[title="View Large"]').click(); await page.locator('li[title="View Large"]').click();
await expect(pausePlayButton).toHaveClass(/is-paused/); await expect(pausePlayButton).toHaveClass(/is-paused/);
await page.locator('[aria-label="Close"]').click(); await page.getByRole('button', { name: 'Close' }).click();
await expect.soft(pausePlayButton).not.toHaveClass(/is-paused/); await expect.soft(pausePlayButton).not.toHaveClass(/is-paused/);
}); });
@ -361,7 +386,7 @@ test.describe('Example Imagery in Display Layout', () => {
await page.locator('li[title="View Large"]').click(); await page.locator('li[title="View Large"]').click();
await expect(pausePlayButton).toHaveClass(/is-paused/); await expect(pausePlayButton).toHaveClass(/is-paused/);
await page.locator('[aria-label="Close"]').click(); await page.getByRole('button', { name: 'Close' }).click();
await expect.soft(pausePlayButton).toHaveClass(/is-paused/); await expect.soft(pausePlayButton).toHaveClass(/is-paused/);
}); });
@ -372,7 +397,7 @@ test.describe('Example Imagery in Display Layout', () => {
}); });
// Edit mode // Edit mode
await page.click('button[title="Edit"]'); await page.getByLabel('Edit Object').click();
// Click on example imagery to expose toolbar // Click on example imagery to expose toolbar
await page.locator('.c-so-view__header').click(); await page.locator('.c-so-view__header').click();
@ -391,7 +416,7 @@ test.describe('Example Imagery in Display Layout', () => {
test('Resizing the layout changes thumbnail visibility and size', async ({ page }) => { test('Resizing the layout changes thumbnail visibility and size', async ({ page }) => {
const thumbsWrapperLocator = page.locator('.c-imagery__thumbs-wrapper'); const thumbsWrapperLocator = page.locator('.c-imagery__thumbs-wrapper');
// Edit mode // Edit mode
await page.click('button[title="Edit"]'); await page.getByLabel('Edit Object').click();
// Click on example imagery to expose toolbar // Click on example imagery to expose toolbar
await page.locator('.c-so-view__header').click(); await page.locator('.c-so-view__header').click();
@ -460,6 +485,33 @@ test.describe('Example Imagery in Display Layout', () => {
test.describe('Example Imagery in Flexible layout', () => { test.describe('Example Imagery in Flexible layout', () => {
let flexibleLayout; let flexibleLayout;
test.beforeEach(async ({ page }) => {
await page.goto('./', { waitUntil: 'domcontentloaded' });
flexibleLayout = await createDomainObjectWithDefaults(page, { type: 'Flexible Layout' });
// Create Example Imagery inside the Flexible Layout
await createDomainObjectWithDefaults(page, {
type: 'Example Imagery',
parent: flexibleLayout.uuid
});
// Navigate back to Flexible Layout
await page.goto(flexibleLayout.url);
});
test('Can double-click on the image to view large image', async ({ page }) => {
// Double-click on the image to open large view
const imageElement = await page.getByRole('button', { name: 'Image Wrapper' });
await imageElement.dblclick();
// Check if the large view is visible
await page.getByRole('button', { name: 'Background Image', state: 'visible' });
// Close the large view
await page.getByRole('button', { name: 'Close' }).click();
});
test.beforeEach(async ({ page }) => { test.beforeEach(async ({ page }) => {
await page.goto('./', { waitUntil: 'domcontentloaded' }); await page.goto('./', { waitUntil: 'domcontentloaded' });
@ -468,7 +520,7 @@ test.describe('Example Imagery in Flexible layout', () => {
/* Create Sine Wave Generator with minimum Image Load Delay */ /* Create Sine Wave Generator with minimum Image Load Delay */
// Click the Create button // Click the Create button
await page.click('button:has-text("Create")'); await page.getByRole('button', { name: 'Create' }).click();
// Click text=Example Imagery // Click text=Example Imagery
await page.click('li[role="menuitem"]:has-text("Example Imagery")'); await page.click('li[role="menuitem"]:has-text("Example Imagery")');
@ -512,7 +564,7 @@ test.describe('Example Imagery in Tabs View', () => {
/* Create Sine Wave Generator with minimum Image Load Delay */ /* Create Sine Wave Generator with minimum Image Load Delay */
// Click the Create button // Click the Create button
await page.click('button:has-text("Create")'); await page.getByRole('button', { name: 'Create' }).click();
// Click text=Example Imagery // Click text=Example Imagery
await page.click('li[role="menuitem"]:has-text("Example Imagery")'); await page.click('li[role="menuitem"]:has-text("Example Imagery")');
@ -555,6 +607,7 @@ test.describe('Example Imagery in Time Strip', () => {
// Navigate to timestrip // Navigate to timestrip
await page.goto(timeStripObject.url); await page.goto(timeStripObject.url);
}); });
test('Clicking a thumbnail loads the image in large view', async ({ page, browserName }) => { test('Clicking a thumbnail loads the image in large view', async ({ page, browserName }) => {
test.info().annotations.push({ test.info().annotations.push({
type: 'issue', type: 'issue',
@ -959,7 +1012,7 @@ async function resetImageryPanAndZoom(page) {
*/ */
async function createImageryView(page) { async function createImageryView(page) {
// Click the Create button // Click the Create button
await page.click('button:has-text("Create")'); await page.getByRole('button', { name: 'Create' }).click();
// Click text=Example Imagery // Click text=Example Imagery
await page.click('li[role="menuitem"]:has-text("Example Imagery")'); await page.click('li[role="menuitem"]:has-text("Example Imagery")');

View File

@ -1,5 +1,5 @@
/***************************************************************************** /*****************************************************************************
* Open MCT, Copyright (c) 2014-2023, United States Government * Open MCT, Copyright (c) 2014-2024, United States Government
* as represented by the Administrator of the National Aeronautics and Space * as represented by the Administrator of the National Aeronautics and Space
* Administration. All rights reserved. * Administration. All rights reserved.
* *
@ -24,36 +24,182 @@
This test suite is dedicated to tests which verify the basic operations surrounding exportAsJSON. This test suite is dedicated to tests which verify the basic operations surrounding exportAsJSON.
*/ */
// FIXME: Remove this eslint exception once tests are implemented import fs from 'fs/promises';
// eslint-disable-next-line no-unused-vars
const { test, expect } = require('../../../../baseFixtures'); import {
createDomainObjectWithDefaults,
openObjectTreeContextMenu
} from '../../../../appActions.js';
import { expect, test } from '../../../../baseFixtures.js';
import { navigateToFaultManagementWithExample } from '../../../../helper/faultUtils.js';
test.describe('ExportAsJSON', () => { test.describe('ExportAsJSON', () => {
test.fixme( let folder;
'Create a basic object and verify that it can be exported as JSON from Tree', test.beforeEach(async ({ page }) => {
async ({ page }) => { // Go to baseURL
//Create domain object await page.goto('./');
//Save Domain Object // Perform actions to create the domain object
//Verify that the newly created domain object can be exported as JSON from the Tree folder = await createDomainObjectWithDefaults(page, {
} type: 'Folder',
); name: 'e2e folder'
test.fixme( });
'Create a basic object and verify that it can be exported as JSON from 3 dot menu', });
async ({ page }) => { test('Create a basic object and verify that it can be exported as JSON from Tree', async ({
//Create domain object page
//Save Domain Object }) => {
//Verify that the newly created domain object can be exported as JSON from the 3 dot menu // Navigate to the page
} await page.goto(folder.url);
);
test.fixme('Verify that a nested Object can be exported as JSON', async ({ page }) => { // Open context menu and initiate download
// Create 2 objects with hierarchy await openObjectTreeContextMenu(page, folder.url);
// Export as JSON const [download] = await Promise.all([
// Verify Hierarchy page.waitForEvent('download'), // Waits for the download event
page.getByLabel('Export as JSON').click() // Triggers the download
]);
// Wait for the download process to complete
const path = await download.path();
// Read the contents of the downloaded file using readFile from fs/promises
const fileContents = await fs.readFile(path, 'utf8');
const jsonData = JSON.parse(fileContents);
// Use the function to retrieve the key
const key = getFirstKeyFromOpenMctJson(jsonData);
// Verify the contents of the JSON file
expect(jsonData.openmct[key]).toHaveProperty('name', 'e2e folder');
expect(jsonData.openmct[key]).toHaveProperty('type', 'folder');
});
test('Create a basic object and verify that it can be exported as JSON from 3 dot menu', async ({
page
}) => {
// Navigate to the page
await page.goto(folder.url);
//3 dot menu
await page.getByLabel('More actions').click();
// Open context menu and initiate download
const [download] = await Promise.all([
page.waitForEvent('download'), // Waits for the download event
page.getByLabel('Export as JSON').click() // Triggers the download
]);
// Read the contents of the downloaded file using readFile from fs/promises
const fileContents = await fs.readFile(await download.path(), 'utf8');
const jsonData = JSON.parse(fileContents);
// Use the function to retrieve the key
const key = getFirstKeyFromOpenMctJson(jsonData);
// Verify the contents of the JSON file
expect(jsonData.openmct[key]).toHaveProperty('name', 'e2e folder');
expect(jsonData.openmct[key]).toHaveProperty('type', 'folder');
});
test('Verify that a nested Object can be exported as JSON', async ({ page }) => {
const timer = await createDomainObjectWithDefaults(page, {
type: 'Timer',
name: 'timer',
parent: folder.uuid
});
// Navigate to the page
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
const [download] = await Promise.all([
page.waitForEvent('download'), // Waits for the download event
page.getByLabel('Export as JSON').click() // Triggers the download
]);
// Read the contents of the downloaded file
const fileContents = await fs.readFile(await download.path(), 'utf8');
const jsonData = JSON.parse(fileContents);
// Retrieve the keys for folder and timer
const folderKey = getFirstKeyFromOpenMctJson(jsonData);
const timerKey = jsonData.openmct[folderKey].composition[0].key;
// Verify the folder properties
expect(jsonData.openmct[folderKey]).toHaveProperty('name', 'e2e folder');
expect(jsonData.openmct[folderKey]).toHaveProperty('type', 'folder');
// Verify the timer properties
expect(jsonData.openmct[timerKey]).toHaveProperty('name', 'timer');
expect(jsonData.openmct[timerKey]).toHaveProperty('type', 'timer');
// Verify the composition of the folder includes the timer
expect(jsonData.openmct[folderKey].composition).toEqual(
expect.arrayContaining([expect.objectContaining({ key: timerKey })])
);
}); });
test.fixme(
'Verify that the ExportAsJSON dropdown does not appear for the item X',
async ({ page }) => {
// Other than non-persistable objects
}
);
}); });
test.describe('ExportAsJSON Disabled Actions', () => {
test.beforeEach(async ({ page }) => {
//Use a Fault Management Object which is not composable
await navigateToFaultManagementWithExample(page);
});
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 page.getByRole('treeitem', { name: 'Fault Management' }).click({
button: 'right'
});
await expect(await page.getByLabel('Export as JSON')).toHaveCount(0);
});
});
test.describe('ExportAsJSON ProgressBar @couchdb', () => {
let folder;
test.beforeEach(async ({ page }) => {
await page.goto('./', { waitUntil: 'networkidle' });
// Perform actions to create the domain object
folder = await createDomainObjectWithDefaults(page, {
type: 'Folder'
});
await createDomainObjectWithDefaults(page, {
type: 'Timer',
parent: folder.uuid
});
await createDomainObjectWithDefaults(page, {
type: 'Timer',
parent: folder.uuid
});
});
test('Verify that the ExportAsJSON action creates a progressbar', async ({ page }) => {
// Navigate to the page
await page.goto(folder.url);
//Export My Items to create a large export
await page.getByRole('treeitem', { name: 'My Items' }).click({ button: 'right' });
// Open context menu and initiate download
await Promise.all([
page.getByRole('progressbar'), // This is just a check for the progress bar
page.getByText(
'Do not navigate away from this page or close this browser tab while this message'
), // This is the text associated with the download
page.waitForEvent('download'), // Waits for the download event
page.getByLabel('Export as JSON').click() // Triggers the download
]);
});
});
/**
* Retrieves the first key from the 'openmct' property of the provided JSON object.
*
* @param {Object} jsonData - The JSON object containing the 'openmct' property.
* @returns {string} The first key found in the 'openmct' object.
* @throws {Error} If no keys are found in the 'openmct' object.
*/
function getFirstKeyFromOpenMctJson(jsonData) {
if (!jsonData.openmct) {
throw new Error("The provided JSON object does not have an 'openmct' property.");
}
const keys = Object.keys(jsonData.openmct);
if (keys.length === 0) {
throw new Error('No keys found in the openmct object');
}
return keys[0];
}

View File

@ -1,5 +1,5 @@
/***************************************************************************** /*****************************************************************************
* Open MCT, Copyright (c) 2014-2023, United States Government * Open MCT, Copyright (c) 2014-2024, United States Government
* as represented by the Administrator of the National Aeronautics and Space * as represented by the Administrator of the National Aeronautics and Space
* Administration. All rights reserved. * Administration. All rights reserved.
* *
@ -26,7 +26,7 @@ This test suite is dedicated to tests which verify the basic operations surround
// FIXME: Remove this eslint exception once tests are implemented // FIXME: Remove this eslint exception once tests are implemented
// eslint-disable-next-line no-unused-vars // eslint-disable-next-line no-unused-vars
const { test, expect } = require('../../../../baseFixtures'); import { expect, test } from '../../../../baseFixtures.js';
test.describe('ExportAsJSON', () => { test.describe('ExportAsJSON', () => {
test.fixme('Verify that domain object can be importAsJSON from Tree', async ({ page }) => { test.fixme('Verify that domain object can be importAsJSON from Tree', async ({ page }) => {

View File

@ -1,5 +1,5 @@
/***************************************************************************** /*****************************************************************************
* Open MCT, Copyright (c) 2014-2023, United States Government * Open MCT, Copyright (c) 2014-2024, United States Government
* as represented by the Administrator of the National Aeronautics and Space * as represented by the Administrator of the National Aeronautics and Space
* Administration. All rights reserved. * Administration. All rights reserved.
* *
@ -19,22 +19,24 @@
* this source code distribution or the Licensing information page available * this source code distribution or the Licensing information page available
* at runtime from the About dialog for additional information. * at runtime from the About dialog for additional information.
*****************************************************************************/ *****************************************************************************/
/* global __dirname */
const { test, expect } = require('../../../../pluginFixtures'); import { fileURLToPath } from 'url';
const { createDomainObjectWithDefaults } = require('../../../../appActions');
const path = require('path'); import { createDomainObjectWithDefaults } from '../../../../appActions.js';
import { expect, test } from '../../../../pluginFixtures.js';
test.describe('Testing numeric data with inspector data visualization (i.e., data pivoting)', () => { test.describe('Testing numeric data with inspector data visualization (i.e., data pivoting)', () => {
test.beforeEach(async ({ page }) => { test.beforeEach(async ({ page }) => {
// eslint-disable-next-line no-undef // eslint-disable-next-line no-undef
await page.addInitScript({ await page.addInitScript({
path: path.join(__dirname, '../../../../helper/', 'addInitDataVisualization.js') path: fileURLToPath(
new URL('../../../../helper/addInitDataVisualization.js', import.meta.url)
)
}); });
await page.goto('./', { waitUntil: 'domcontentloaded' }); await page.goto('./', { waitUntil: 'domcontentloaded' });
}); });
test('Can click on telemetry and see data in inspector', async ({ page }) => { test('Can click on telemetry and see data in inspector @2p', async ({ page, context }) => {
const exampleDataVisualizationSource = await createDomainObjectWithDefaults(page, { const exampleDataVisualizationSource = await createDomainObjectWithDefaults(page, {
type: 'Example Data Visualization Source' type: 'Example Data Visualization Source'
}); });
@ -65,5 +67,16 @@ test.describe('Testing numeric data with inspector data visualization (i.e., dat
page.locator('span.plot-series-name', { hasText: 'Second Sine Wave Generator Hz' }) page.locator('span.plot-series-name', { hasText: 'Second Sine Wave Generator Hz' })
).toBeVisible(); ).toBeVisible();
await expect(page.locator('.js-series-data-loaded')).toBeVisible(); await expect(page.locator('.js-series-data-loaded')).toBeVisible();
// test new tab
await page.getByLabel('Inspector Views').getByLabel('More actions').click();
const pagePromise = context.waitForEvent('page');
await page.getByRole('menuitem', { name: /Open In New Tab/ }).click();
// ensure our new tab's title is correct
const newPage = await pagePromise;
await newPage.waitForLoadState();
// expect new tab title to contain 'Second Sine Wave Generator'
await expect(newPage).toHaveTitle('Second Sine Wave Generator');
}); });
}); });

View File

@ -1,5 +1,5 @@
/***************************************************************************** /*****************************************************************************
* Open MCT, Copyright (c) 2014-2023, United States Government * Open MCT, Copyright (c) 2014-2024, United States Government
* as represented by the Administrator of the National Aeronautics and Space * as represented by the Administrator of the National Aeronautics and Space
* Administration. All rights reserved. * Administration. All rights reserved.
* *
@ -20,14 +20,14 @@
* at runtime from the About dialog for additional information. * at runtime from the About dialog for additional information.
*****************************************************************************/ *****************************************************************************/
const { test, expect } = require('../../../../pluginFixtures'); import {
const {
createDomainObjectWithDefaults, createDomainObjectWithDefaults,
setStartOffset, openObjectTreeContextMenu,
setFixedTimeMode, setFixedTimeMode,
setRealTimeMode, setRealTimeMode,
openObjectTreeContextMenu setStartOffset
} = require('../../../../appActions'); } from '../../../../appActions.js';
import { expect, test } from '../../../../pluginFixtures.js';
test.describe('Testing LAD table configuration', () => { test.describe('Testing LAD table configuration', () => {
let ladTable; let ladTable;
@ -52,13 +52,13 @@ test.describe('Testing LAD table configuration', () => {
}); });
test('in edit mode, LAD Tables provide ability to hide columns', async ({ page }) => { test('in edit mode, LAD Tables provide ability to hide columns', async ({ page }) => {
// Edit LAD table // Edit LAD table
await page.locator('[title="Edit"]').click(); await page.getByLabel('Edit Object').click();
await page.getByRole('tab', { name: 'LAD Table Configuration' }).click(); await page.getByRole('tab', { name: 'LAD Table Configuration' }).click();
// make sure headers are visible initially // make sure headers are visible initially
await expect(page.getByRole('cell', { name: 'Timestamp' })).toBeVisible(); await expect(page.getByRole('cell', { name: 'Timestamp', exact: true })).toBeVisible();
await expect(page.getByRole('cell', { name: 'Units' })).toBeVisible(); await expect(page.getByRole('cell', { name: 'Units' })).toBeVisible();
await expect(page.getByRole('cell', { name: 'Type' })).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: 'WATCH' })).toBeVisible();
await expect(page.getByRole('cell', { name: 'WARNING' })).toBeVisible(); await expect(page.getByRole('cell', { name: 'WARNING' })).toBeVisible();
await expect(page.getByRole('cell', { name: 'DISTRESS' })).toBeVisible(); await expect(page.getByRole('cell', { name: 'DISTRESS' })).toBeVisible();
@ -66,10 +66,10 @@ test.describe('Testing LAD table configuration', () => {
await expect(page.getByRole('cell', { name: 'SEVERE' })).toBeVisible(); await expect(page.getByRole('cell', { name: 'SEVERE' })).toBeVisible();
// hide timestamp column // hide timestamp column
await page.getByLabel('Timestamp').uncheck(); await page.getByLabel('Timestamp', { exact: true }).uncheck();
await expect(page.getByRole('cell', { name: 'Timestamp' })).toBeHidden(); await expect(page.getByRole('cell', { name: 'Timestamp', exact: true })).toBeHidden();
await expect(page.getByRole('cell', { name: 'Units' })).toBeVisible(); await expect(page.getByRole('cell', { name: 'Units' })).toBeVisible();
await expect(page.getByRole('cell', { name: 'Type' })).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: 'WATCH' })).toBeVisible();
await expect(page.getByRole('cell', { name: 'WARNING' })).toBeVisible(); await expect(page.getByRole('cell', { name: 'WARNING' })).toBeVisible();
await expect(page.getByRole('cell', { name: 'DISTRESS' })).toBeVisible(); await expect(page.getByRole('cell', { name: 'DISTRESS' })).toBeVisible();
@ -78,10 +78,10 @@ test.describe('Testing LAD table configuration', () => {
// hide units & type column // hide units & type column
await page.getByLabel('Units').uncheck(); await page.getByLabel('Units').uncheck();
await page.getByLabel('Type').uncheck(); await page.getByLabel('Type', { exact: true }).uncheck();
await expect(page.getByRole('cell', { name: 'Timestamp' })).toBeHidden(); await expect(page.getByRole('cell', { name: 'Timestamp', exact: true })).toBeHidden();
await expect(page.getByRole('cell', { name: 'Units' })).toBeHidden(); await expect(page.getByRole('cell', { name: 'Units' })).toBeHidden();
await expect(page.getByRole('cell', { name: 'Type' })).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: 'WATCH' })).toBeVisible();
await expect(page.getByRole('cell', { name: 'WARNING' })).toBeVisible(); await expect(page.getByRole('cell', { name: 'WARNING' })).toBeVisible();
await expect(page.getByRole('cell', { name: 'DISTRESS' })).toBeVisible(); await expect(page.getByRole('cell', { name: 'DISTRESS' })).toBeVisible();
@ -90,9 +90,9 @@ test.describe('Testing LAD table configuration', () => {
// hide WATCH column // hide WATCH column
await page.getByLabel('WATCH').uncheck(); await page.getByLabel('WATCH').uncheck();
await expect(page.getByRole('cell', { name: 'Timestamp' })).toBeHidden(); await expect(page.getByRole('cell', { name: 'Timestamp', exact: true })).toBeHidden();
await expect(page.getByRole('cell', { name: 'Units' })).toBeHidden(); await expect(page.getByRole('cell', { name: 'Units' })).toBeHidden();
await expect(page.getByRole('cell', { name: 'Type' })).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: 'WATCH' })).toBeHidden();
await expect(page.getByRole('cell', { name: 'WARNING' })).toBeVisible(); await expect(page.getByRole('cell', { name: 'WARNING' })).toBeVisible();
await expect(page.getByRole('cell', { name: 'DISTRESS' })).toBeVisible(); await expect(page.getByRole('cell', { name: 'DISTRESS' })).toBeVisible();
@ -103,9 +103,9 @@ test.describe('Testing LAD table configuration', () => {
await page.locator('button[title="Save"]').click(); await page.locator('button[title="Save"]').click();
await page.getByRole('listitem', { name: 'Save and Finish Editing' }).click(); await page.getByRole('listitem', { name: 'Save and Finish Editing' }).click();
await page.reload(); await page.reload();
await expect(page.getByRole('cell', { name: 'Timestamp' })).toBeHidden(); await expect(page.getByRole('cell', { name: 'Timestamp', exact: true })).toBeHidden();
await expect(page.getByRole('cell', { name: 'Units' })).toBeHidden(); await expect(page.getByRole('cell', { name: 'Units' })).toBeHidden();
await expect(page.getByRole('cell', { name: 'Type' })).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: 'WATCH' })).toBeHidden();
await expect(page.getByRole('cell', { name: 'WARNING' })).toBeVisible(); await expect(page.getByRole('cell', { name: 'WARNING' })).toBeVisible();
await expect(page.getByRole('cell', { name: 'DISTRESS' })).toBeVisible(); await expect(page.getByRole('cell', { name: 'DISTRESS' })).toBeVisible();
@ -113,14 +113,14 @@ test.describe('Testing LAD table configuration', () => {
await expect(page.getByRole('cell', { name: 'SEVERE' })).toBeVisible(); await expect(page.getByRole('cell', { name: 'SEVERE' })).toBeVisible();
// Edit LAD table // Edit LAD table
await page.locator('[title="Edit"]').click(); await page.getByLabel('Edit Object').click();
await page.getByRole('tab', { name: 'LAD Table Configuration' }).click(); await page.getByRole('tab', { name: 'LAD Table Configuration' }).click();
// show timestamp column // show timestamp column
await page.getByLabel('Timestamp').check(); await page.getByLabel('Timestamp', { exact: true }).check();
await expect(page.getByRole('cell', { name: 'Units' })).toBeHidden(); await expect(page.getByRole('cell', { name: 'Units' })).toBeHidden();
await expect(page.getByRole('cell', { name: 'Type' })).toBeHidden(); await expect(page.getByRole('cell', { name: 'Type', exact: true })).toBeHidden();
await expect(page.getByRole('cell', { name: 'Timestamp' })).toBeVisible(); await expect(page.getByRole('cell', { name: 'Timestamp', exact: true })).toBeVisible();
await expect(page.getByRole('cell', { name: 'WATCH' })).toBeHidden(); await expect(page.getByRole('cell', { name: 'WATCH' })).toBeHidden();
await expect(page.getByRole('cell', { name: 'WARNING' })).toBeVisible(); await expect(page.getByRole('cell', { name: 'WARNING' })).toBeVisible();
await expect(page.getByRole('cell', { name: 'DISTRESS' })).toBeVisible(); await expect(page.getByRole('cell', { name: 'DISTRESS' })).toBeVisible();
@ -132,8 +132,8 @@ test.describe('Testing LAD table configuration', () => {
await page.getByRole('listitem', { name: 'Save and Finish Editing' }).click(); await page.getByRole('listitem', { name: 'Save and Finish Editing' }).click();
await page.reload(); await page.reload();
await expect(page.getByRole('cell', { name: 'Units' })).toBeHidden(); await expect(page.getByRole('cell', { name: 'Units' })).toBeHidden();
await expect(page.getByRole('cell', { name: 'Type' })).toBeHidden(); await expect(page.getByRole('cell', { name: 'Type', exact: true })).toBeHidden();
await expect(page.getByRole('cell', { name: 'Timestamp' })).toBeVisible(); await expect(page.getByRole('cell', { name: 'Timestamp', exact: true })).toBeVisible();
await expect(page.getByRole('cell', { name: 'WATCH' })).toBeHidden(); await expect(page.getByRole('cell', { name: 'WATCH' })).toBeHidden();
await expect(page.getByRole('cell', { name: 'WARNING' })).toBeVisible(); await expect(page.getByRole('cell', { name: 'WARNING' })).toBeVisible();
await expect(page.getByRole('cell', { name: 'DISTRESS' })).toBeVisible(); await expect(page.getByRole('cell', { name: 'DISTRESS' })).toBeVisible();
@ -141,16 +141,16 @@ test.describe('Testing LAD table configuration', () => {
await expect(page.getByRole('cell', { name: 'SEVERE' })).toBeVisible(); await expect(page.getByRole('cell', { name: 'SEVERE' })).toBeVisible();
// Edit LAD table // Edit LAD table
await page.locator('[title="Edit"]').click(); await page.getByLabel('Edit Object').click();
await page.getByRole('tab', { name: 'LAD Table Configuration' }).click(); await page.getByRole('tab', { name: 'LAD Table Configuration' }).click();
// show units, type, and WATCH columns // show units, type, and WATCH columns
await page.getByLabel('Units').check(); await page.getByLabel('Units').check();
await page.getByLabel('Type').check(); await page.getByLabel('Type', { exact: true }).check();
await page.getByLabel('WATCH').check(); await page.getByLabel('WATCH').check();
await expect(page.getByRole('cell', { name: 'Timestamp' })).toBeVisible(); await expect(page.getByRole('cell', { name: 'Timestamp', exact: true })).toBeVisible();
await expect(page.getByRole('cell', { name: 'Units' })).toBeVisible(); await expect(page.getByRole('cell', { name: 'Units' })).toBeVisible();
await expect(page.getByRole('cell', { name: 'Type' })).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: 'WATCH' })).toBeVisible();
await expect(page.getByRole('cell', { name: 'WARNING' })).toBeVisible(); await expect(page.getByRole('cell', { name: 'WARNING' })).toBeVisible();
await expect(page.getByRole('cell', { name: 'DISTRESS' })).toBeVisible(); await expect(page.getByRole('cell', { name: 'DISTRESS' })).toBeVisible();
@ -161,9 +161,9 @@ test.describe('Testing LAD table configuration', () => {
await page.locator('button[title="Save"]').click(); await page.locator('button[title="Save"]').click();
await page.getByRole('listitem', { name: 'Save and Finish Editing' }).click(); await page.getByRole('listitem', { name: 'Save and Finish Editing' }).click();
await page.reload(); await page.reload();
await expect(page.getByRole('cell', { name: 'Timestamp' })).toBeVisible(); await expect(page.getByRole('cell', { name: 'Timestamp', exact: true })).toBeVisible();
await expect(page.getByRole('cell', { name: 'Units' })).toBeVisible(); await expect(page.getByRole('cell', { name: 'Units' })).toBeVisible();
await expect(page.getByRole('cell', { name: 'Type' })).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: 'WATCH' })).toBeVisible();
await expect(page.getByRole('cell', { name: 'WARNING' })).toBeVisible(); await expect(page.getByRole('cell', { name: 'WARNING' })).toBeVisible();
await expect(page.getByRole('cell', { name: 'DISTRESS' })).toBeVisible(); await expect(page.getByRole('cell', { name: 'DISTRESS' })).toBeVisible();
@ -181,13 +181,13 @@ test.describe('Testing LAD table configuration', () => {
await page.goto(ladTable.url); await page.goto(ladTable.url);
// Edit LAD table // Edit LAD table
await page.getByLabel('Edit').click(); await page.getByLabel('Edit Object').click();
await page.getByRole('tab', { name: 'LAD Table Configuration' }).click(); await page.getByRole('tab', { name: 'LAD Table Configuration' }).click();
// make sure Sine Wave headers are visible initially too // make sure Sine Wave headers are visible initially too
await expect(page.getByRole('cell', { name: 'Timestamp' })).toBeVisible(); await expect(page.getByRole('cell', { name: 'Timestamp', exact: true })).toBeVisible();
await expect(page.getByRole('cell', { name: 'Units' })).toBeVisible(); await expect(page.getByRole('cell', { name: 'Units' })).toBeVisible();
await expect(page.getByRole('cell', { name: 'Type' })).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: 'WATCH' })).toBeVisible();
await expect(page.getByRole('cell', { name: 'WARNING' })).toBeVisible(); await expect(page.getByRole('cell', { name: 'WARNING' })).toBeVisible();
await expect(page.getByRole('cell', { name: 'DISTRESS' })).toBeVisible(); await expect(page.getByRole('cell', { name: 'DISTRESS' })).toBeVisible();
@ -198,16 +198,16 @@ test.describe('Testing LAD table configuration', () => {
await page.getByLabel('Save').click(); await page.getByLabel('Save').click();
await page.getByRole('listitem', { name: 'Save and Finish Editing' }).click(); await page.getByRole('listitem', { name: 'Save and Finish Editing' }).click();
// Remove Sin Wave Generator // Remove Sine Wave Generator
openObjectTreeContextMenu(page, sineWaveObject.url); openObjectTreeContextMenu(page, sineWaveObject.url);
await page.getByRole('menuitem', { name: /Remove/ }).click(); await page.getByRole('menuitem', { name: /Remove/ }).click();
await page.getByRole('button', { name: 'OK' }).click(); await page.getByRole('button', { name: 'OK', exact: true }).click();
// Ensure Units & Limit columns are gone // Ensure Units & Limit columns are gone
// as Event Generator don't have them // as Event Generator don't have them
await page.goto(ladTable.url); await page.goto(ladTable.url);
await expect(page.getByRole('cell', { name: 'Timestamp' })).toBeVisible(); await expect(page.getByRole('cell', { name: 'Timestamp', exact: true })).toBeVisible();
await expect(page.getByRole('cell', { name: 'Type' })).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: 'Units' })).toBeHidden();
await expect(page.getByRole('cell', { name: 'WATCH' })).toBeHidden(); await expect(page.getByRole('cell', { name: 'WATCH' })).toBeHidden();
await expect(page.getByRole('cell', { name: 'WARNING' })).toBeHidden(); await expect(page.getByRole('cell', { name: 'WARNING' })).toBeHidden();
@ -258,7 +258,7 @@ test.describe('Testing LAD table @unstable', () => {
name: 'Test LAD Table' name: 'Test LAD Table'
}); });
// Edit LAD table // Edit LAD table
await page.locator('[title="Edit"]').click(); await page.getByLabel('Edit Object').click();
// Expand the 'My Items' folder in the left tree // Expand the 'My Items' folder in the left tree
await page.locator('.c-tree__item__view-control.c-disclosure-triangle').click(); await page.locator('.c-tree__item__view-control.c-disclosure-triangle').click();
@ -286,7 +286,7 @@ test.describe('Testing LAD table @unstable', () => {
name: 'Test LAD Table' name: 'Test LAD Table'
}); });
// Edit LAD table // Edit LAD table
await page.locator('[title="Edit"]').click(); await page.getByLabel('Edit Object').click();
// Expand the 'My Items' folder in the left tree // Expand the 'My Items' folder in the left tree
await page.locator('.c-tree__item__view-control.c-disclosure-triangle').click(); await page.locator('.c-tree__item__view-control.c-disclosure-triangle').click();

View File

@ -0,0 +1,69 @@
/*****************************************************************************
* Open MCT, Copyright (c) 2014-2024, United States Government
* as represented by the Administrator of the National Aeronautics and Space
* Administration. All rights reserved.
*
* Open MCT is licensed under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0.
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*
* Open MCT includes source code licensed under additional open source
* licenses. See the Open Source Licenses file (LICENSES.md) included with
* this source code distribution or the Licensing information page available
* at runtime from the About dialog for additional information.
*****************************************************************************/
import { createDomainObjectWithDefaults } from '../../../../appActions.js';
import { expect, test } from '../../../../pluginFixtures.js';
test.describe('LAD Table Sets', () => {
test('Ensure we have numbers in cells', async ({ page }) => {
await page.goto('./', { waitUntil: 'domcontentloaded' });
const ladTableSet = await createDomainObjectWithDefaults(page, {
type: 'LAD Table Set'
});
const firstLadTable = await createDomainObjectWithDefaults(page, {
type: 'LAD Table',
parent: ladTableSet.uuid
});
await createDomainObjectWithDefaults(page, {
type: 'Sine Wave Generator',
parent: firstLadTable.uuid
});
const secondLadTable = await createDomainObjectWithDefaults(page, {
type: 'LAD Table',
parent: ladTableSet.uuid
});
await createDomainObjectWithDefaults(page, {
type: 'Sine Wave Generator',
parent: secondLadTable.uuid
});
await page.goto(ladTableSet.url);
// Wait for the initial value to show after mount
await expect(page.getByLabel('lad value').first()).not.toContainText('---');
const valueFromFirstSineWave = await page.getByLabel('lad value').first().innerText();
const firstSineWaveNumber = parseFloat(valueFromFirstSineWave);
// ensure we have a float value in the cell and it's finite
expect(Number.isFinite(firstSineWaveNumber)).toBeTruthy();
const valueFromSecondSineWave = await page.getByLabel('lad value').last().innerText();
const secondSineWaveNumber = parseFloat(valueFromSecondSineWave);
// ensure we have a float value in the cell and it's finite
expect(Number.isFinite(secondSineWaveNumber)).toBeTruthy();
});
});

View File

@ -1,5 +1,5 @@
/***************************************************************************** /*****************************************************************************
* Open MCT, Copyright (c) 2014-2023, United States Government * Open MCT, Copyright (c) 2014-2024, United States Government
* as represented by the Administrator of the National Aeronautics and Space * as represented by the Administrator of the National Aeronautics and Space
* Administration. All rights reserved. * Administration. All rights reserved.
* *
@ -19,15 +19,16 @@
* this source code distribution or the Licensing information page available * this source code distribution or the Licensing information page available
* at runtime from the About dialog for additional information. * at runtime from the About dialog for additional information.
*****************************************************************************/ *****************************************************************************/
/* global __dirname */
/* /*
This test suite is dedicated to tests which verify the basic operations surrounding Notebooks. This test suite is dedicated to tests which verify the basic operations surrounding Notebooks.
*/ */
const { test, expect, streamToString } = require('../../../../pluginFixtures'); import { fileURLToPath } from 'url';
const { createDomainObjectWithDefaults } = require('../../../../appActions');
const nbUtils = require('../../../../helper/notebookUtils'); import { createDomainObjectWithDefaults } from '../../../../appActions.js';
const path = require('path'); import * as nbUtils from '../../../../helper/notebookUtils.js';
import { expect, streamToString, test } from '../../../../pluginFixtures.js';
const NOTEBOOK_NAME = 'Notebook'; const NOTEBOOK_NAME = 'Notebook';
@ -246,7 +247,7 @@ test.describe('Notebook export tests', () => {
test('can export notebook as text', async ({ page }) => { test('can export notebook as text', async ({ page }) => {
await nbUtils.enterTextEntry(page, `Foo bar entry`); await nbUtils.enterTextEntry(page, `Foo bar entry`);
// Click on 3 Dot Menu // Click on 3 Dot Menu
await page.locator('button[title="More options"]').click(); await page.locator('button[title="More actions"]').click();
const downloadPromise = page.waitForEvent('download'); const downloadPromise = page.waitForEvent('download');
await page.getByRole('menuitem', { name: /Export Notebook as Text/ }).click(); await page.getByRole('menuitem', { name: /Export Notebook as Text/ }).click();
@ -278,7 +279,7 @@ test.describe('Notebook entry tests', () => {
test.beforeEach(async ({ page }) => { test.beforeEach(async ({ page }) => {
// eslint-disable-next-line no-undef // eslint-disable-next-line no-undef
await page.addInitScript({ await page.addInitScript({
path: path.join(__dirname, '../../../../helper/', 'addInitNotebookWithUrls.js') path: fileURLToPath(new URL('../../../../helper/addInitNotebookWithUrls.js', import.meta.url))
}); });
await page.goto('./', { waitUntil: 'domcontentloaded' }); await page.goto('./', { waitUntil: 'domcontentloaded' });

View File

@ -0,0 +1,147 @@
/*****************************************************************************
* 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 Notebooks.
*/
import fs from 'fs/promises';
import { fileURLToPath } from 'url';
import { createDomainObjectWithDefaults } from '../../../../appActions.js';
import { expect, test } from '../../../../pluginFixtures.js';
const NOTEBOOK_NAME = 'Notebook';
test.describe('Snapshot image tests', () => {
test.beforeEach(async ({ page }) => {
//Navigate to baseURL
await page.goto('./', { waitUntil: 'domcontentloaded' });
// Create Notebook
await createDomainObjectWithDefaults(page, {
type: NOTEBOOK_NAME
});
});
test('Can drop an image onto a notebook and create a new entry', async ({ page }) => {
const imageData = await fs.readFile(
fileURLToPath(
new URL('../../../../../src/images/favicons/favicon-96x96.png', import.meta.url)
)
);
const imageArray = new Uint8Array(imageData);
const fileData = Array.from(imageArray);
const dropTransfer = await page.evaluateHandle((data) => {
const dataTransfer = new DataTransfer();
const file = new File([new Uint8Array(data)], 'favicon-96x96.png', { type: 'image/png' });
dataTransfer.items.add(file);
return dataTransfer;
}, fileData);
await page.dispatchEvent('.c-notebook__drag-area', 'drop', { dataTransfer: dropTransfer });
await page.locator('.c-ne__save-button > button').click();
// be sure that entry was created
await expect(page.getByText('favicon-96x96.png')).toBeVisible();
await page.getByRole('img', { name: 'favicon-96x96.png thumbnail' }).click();
// expect large image to be displayed
await expect(page.getByRole('dialog').getByText('favicon-96x96.png')).toBeVisible();
await page.getByRole('button', { name: 'Close' }).click();
// drop another image onto the entry
await page.dispatchEvent('.c-snapshots', 'drop', { dataTransfer: dropTransfer });
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 page.locator('.c-snapshot.c-ne__embed').first().getByTitle('More actions').click();
await page.getByRole('menuitem', { name: /Remove This Embed/ }).click();
await page.getByRole('button', { name: 'Ok', exact: true }).click();
// Ensure that the thumbnail is removed before we assert
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);
});
});
test.describe('Snapshot image failure tests', () => {
test.use({ failOnConsoleError: false });
test.beforeEach(async ({ page }) => {
//Navigate to baseURL
await page.goto('./', { waitUntil: 'domcontentloaded' });
// Create Notebook
await createDomainObjectWithDefaults(page, {
type: NOTEBOOK_NAME
});
});
test('Get an error notification when dropping unknown file onto notebook entry', async ({
page
}) => {
// fill Uint8Array array with some garbage data
const garbageData = new Uint8Array(100);
const fileData = Array.from(garbageData);
const dropTransfer = await page.evaluateHandle((data) => {
const dataTransfer = new DataTransfer();
const file = new File([new Uint8Array(data)], 'someGarbage.foo', { type: 'unknown/garbage' });
dataTransfer.items.add(file);
return dataTransfer;
}, fileData);
await page.dispatchEvent('.c-notebook__drag-area', 'drop', { dataTransfer: dropTransfer });
// should have gotten a notification from OpenMCT that we couldn't add it
await expect(page.getByText('Unknown object(s) dropped and cannot embed')).toBeVisible();
});
test('Get an error notification when dropping big files onto notebook entry', async ({
page
}) => {
const garbageSize = 15 * 1024 * 1024; // 15 megabytes
await page.addScriptTag({
// make the garbage client side
content: `window.bigGarbageData = new Uint8Array(${garbageSize})`
});
const bigDropTransfer = await page.evaluateHandle(() => {
const dataTransfer = new DataTransfer();
const file = new File([window.bigGarbageData], 'bigBoy.png', { type: 'image/png' });
dataTransfer.items.add(file);
return dataTransfer;
});
await page.dispatchEvent('.c-notebook__drag-area', 'drop', { dataTransfer: bigDropTransfer });
// should have gotten a notification from OpenMCT that we couldn't add it as it's too big
await expect(page.getByText('unable to embed')).toBeVisible();
});
});

View File

@ -1,5 +1,5 @@
/***************************************************************************** /*****************************************************************************
* Open MCT, Copyright (c) 2014-2023, United States Government * Open MCT, Copyright (c) 2014-2024, United States Government
* as represented by the Administrator of the National Aeronautics and Space * as represented by the Administrator of the National Aeronautics and Space
* Administration. All rights reserved. * Administration. All rights reserved.
* *
@ -19,17 +19,12 @@
* this source code distribution or the Licensing information page available * this source code distribution or the Licensing information page available
* at runtime from the About dialog for additional information. * at runtime from the About dialog for additional information.
*****************************************************************************/ *****************************************************************************/
/* global __dirname */
/* /*
This test suite is dedicated to tests which verify the basic operations surrounding Notebooks. This test suite is dedicated to tests which verify the basic operations surrounding Notebooks.
*/ */
const fs = require('fs').promises; import { expect, test } from '../../../../pluginFixtures.js';
const path = require('path');
const { test, expect } = require('../../../../pluginFixtures');
const { createDomainObjectWithDefaults } = require('../../../../appActions');
const NOTEBOOK_NAME = 'Notebook';
test.describe('Snapshot Menu tests', () => { test.describe('Snapshot Menu tests', () => {
test.fixme( test.fixme(
@ -88,23 +83,19 @@ test.describe('Snapshot Container tests', () => {
// name: "Dropped Overlay Plot" // name: "Dropped Overlay Plot"
// }); // });
await page.getByRole('button', { name: ' Snapshot ' }).click(); await page.getByLabel('Take a Notebook Snapshot').click();
await page.getByRole('menuitem', { name: 'Save to Notebook Snapshots' }).click(); await page.getByRole('menuitem', { name: 'Save to Notebook Snapshots' }).click();
await page.getByRole('button', { name: 'Show' }).click(); await page.getByLabel('Show Snapshots').click();
});
test('A snapshot can be Quick Viewed from Container with 3 dot action menu', async ({ page }) => {
await page.locator('.c-snapshot.c-ne__embed').first().getByTitle('More actions').click();
await page.getByRole('menuitem', { name: 'Quick View' }).click();
await expect(page.locator('.c-overlay__outer')).toBeVisible();
}); });
test.fixme('5 Snapshots can be added to a container', async ({ page }) => {});
test.fixme(
'5 Snapshots can be added to a container and Deleted with Delete All action',
async ({ page }) => {}
);
test.fixme(
'A snapshot can be Deleted from Container with 3 dot action menu',
async ({ page }) => {}
);
test.fixme( test.fixme(
'A snapshot can be Viewed, Annotated, display deleted, and saved from Container with 3 dot action menu', 'A snapshot can be Viewed, Annotated, display deleted, and saved from Container with 3 dot action menu',
async ({ page }) => { async ({ page }) => {
await page.locator('.c-snapshot.c-ne__embed').first().getByTitle('More options').click(); await page.locator('.c-snapshot.c-ne__embed').first().getByTitle('More actions').click();
await page.getByRole('menuitem', { name: ' View Snapshot' }).click(); await page.getByRole('menuitem', { name: ' View Snapshot' }).click();
await expect(page.locator('.c-overlay__outer')).toBeVisible(); await expect(page.locator('.c-overlay__outer')).toBeVisible();
await page.getByTitle('Annotate').click(); await page.getByTitle('Annotate').click();
@ -116,11 +107,15 @@ test.describe('Snapshot Container tests', () => {
//await expect(await page.locator) //await expect(await page.locator)
} }
); );
test('A snapshot can be Quick Viewed from Container with 3 dot action menu', async ({ page }) => { test.fixme('5 Snapshots can be added to a container', async ({ page }) => {});
await page.locator('.c-snapshot.c-ne__embed').first().getByTitle('More options').click(); test.fixme(
await page.getByRole('menuitem', { name: 'Quick View' }).click(); '5 Snapshots can be added to a container and Deleted with Delete All action',
await expect(page.locator('.c-overlay__outer')).toBeVisible(); async ({ page }) => {}
}); );
test.fixme(
'A snapshot can be Deleted from Container with 3 dot action menu',
async ({ page }) => {}
);
test.fixme( test.fixme(
'A snapshot can be Navigated To from Container with 3 dot action menu', 'A snapshot can be Navigated To from Container with 3 dot action menu',
async ({ page }) => {} async ({ page }) => {}
@ -164,115 +159,3 @@ test.describe('Snapshot Container tests', () => {
} }
); );
}); });
test.describe('Snapshot image tests', () => {
test.beforeEach(async ({ page }) => {
//Navigate to baseURL
await page.goto('./', { waitUntil: 'domcontentloaded' });
// Create Notebook
await createDomainObjectWithDefaults(page, {
type: NOTEBOOK_NAME
});
});
test('Can drop an image onto a notebook and create a new entry', async ({ page }) => {
const imageData = await fs.readFile(
path.resolve(__dirname, '../../../../../src/images/favicons/favicon-96x96.png')
);
const imageArray = new Uint8Array(imageData);
const fileData = Array.from(imageArray);
const dropTransfer = await page.evaluateHandle((data) => {
const dataTransfer = new DataTransfer();
const file = new File([new Uint8Array(data)], 'favicon-96x96.png', { type: 'image/png' });
dataTransfer.items.add(file);
return dataTransfer;
}, fileData);
await page.dispatchEvent('.c-notebook__drag-area', 'drop', { dataTransfer: dropTransfer });
await page.locator('.c-ne__save-button > button').click();
// be sure that entry was created
await expect(page.getByText('favicon-96x96.png')).toBeVisible();
await page.getByRole('img', { name: 'favicon-96x96.png thumbnail' }).click();
// expect large image to be displayed
await expect(page.getByRole('dialog').getByText('favicon-96x96.png')).toBeVisible();
await page.getByLabel('Close').click();
// drop another image onto the entry
await page.dispatchEvent('.c-snapshots', 'drop', { dataTransfer: dropTransfer });
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 page.locator('.c-snapshot.c-ne__embed').first().getByTitle('More options').click();
await page.getByRole('menuitem', { name: /Remove This Embed/ }).click();
await page.getByRole('button', { name: 'Ok', exact: true }).click();
// Ensure that the thumbnail is removed before we assert
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);
});
});
test.describe('Snapshot image failure tests', () => {
test.use({ failOnConsoleError: false });
test.beforeEach(async ({ page }) => {
//Navigate to baseURL
await page.goto('./', { waitUntil: 'domcontentloaded' });
// Create Notebook
await createDomainObjectWithDefaults(page, {
type: NOTEBOOK_NAME
});
});
test('Get an error notification when dropping unknown file onto notebook entry', async ({
page
}) => {
// fill Uint8Array array with some garbage data
const garbageData = new Uint8Array(100);
const fileData = Array.from(garbageData);
const dropTransfer = await page.evaluateHandle((data) => {
const dataTransfer = new DataTransfer();
const file = new File([new Uint8Array(data)], 'someGarbage.foo', { type: 'unknown/garbage' });
dataTransfer.items.add(file);
return dataTransfer;
}, fileData);
await page.dispatchEvent('.c-notebook__drag-area', 'drop', { dataTransfer: dropTransfer });
// should have gotten a notification from OpenMCT that we couldn't add it
await expect(page.getByText('Unknown object(s) dropped and cannot embed')).toBeVisible();
});
test('Get an error notification when dropping big files onto notebook entry', async ({
page
}) => {
const garbageSize = 15 * 1024 * 1024; // 15 megabytes
await page.addScriptTag({
// make the garbage client side
content: `window.bigGarbageData = new Uint8Array(${garbageSize})`
});
const bigDropTransfer = await page.evaluateHandle(() => {
const dataTransfer = new DataTransfer();
const file = new File([window.bigGarbageData], 'bigBoy.png', { type: 'image/png' });
dataTransfer.items.add(file);
return dataTransfer;
});
await page.dispatchEvent('.c-notebook__drag-area', 'drop', { dataTransfer: bigDropTransfer });
// should have gotten a notification from OpenMCT that we couldn't add it as it's too big
await expect(page.getByText('unable to embed')).toBeVisible();
});
});

View File

@ -1,5 +1,5 @@
/***************************************************************************** /*****************************************************************************
* Open MCT, Copyright (c) 2014-2023, United States Government * Open MCT, Copyright (c) 2014-2024, United States Government
* as represented by the Administrator of the National Aeronautics and Space * as represented by the Administrator of the National Aeronautics and Space
* Administration. All rights reserved. * Administration. All rights reserved.
* *
@ -24,13 +24,13 @@
This test suite is dedicated to tests which verify notebook tag functionality. This test suite is dedicated to tests which verify notebook tag functionality.
*/ */
const { test, expect } = require('../../../../pluginFixtures'); import { createDomainObjectWithDefaults } from '../../../../appActions.js';
const { createDomainObjectWithDefaults } = require('../../../../appActions'); import {
const {
enterTextEntry,
createNotebookAndEntry, createNotebookAndEntry,
createNotebookEntryAndTags createNotebookEntryAndTags,
} = require('../../../../helper/notebookUtils'); enterTextEntry
} from '../../../../helper/notebookUtils.js';
import { expect, test } from '../../../../pluginFixtures.js';
test.describe('Tagging in Notebooks @addInit', () => { test.describe('Tagging in Notebooks @addInit', () => {
test.beforeEach(async ({ page }) => { test.beforeEach(async ({ page }) => {
@ -88,43 +88,53 @@ test.describe('Tagging in Notebooks @addInit', () => {
await page.locator('[placeholder="Type to select tag"]').click(); await page.locator('[placeholder="Type to select tag"]').click();
await page.locator('[aria-label="OpenMCT Search"] input[type="search"]').click(); await page.getByRole('search').getByLabel('Search Input').click();
await expect(page.locator('button:has-text("Add Tag")')).toBeVisible(); await expect(page.locator('button:has-text("Add Tag")')).toBeVisible();
// Test canceling adding a tag after we just click "Add Tag" // Test canceling adding a tag after we just click "Add Tag"
await page.locator('button:has-text("Add Tag")').click(); await page.locator('button:has-text("Add Tag")').click();
await page.locator('[aria-label="OpenMCT Search"] input[type="search"]').click(); await page.getByRole('search').getByLabel('Search Input').click();
await expect(page.locator('button:has-text("Add Tag")')).toBeVisible(); await expect(page.locator('button:has-text("Add Tag")')).toBeVisible();
}); });
test('Can search for tags and preview works properly', async ({ page }) => { test('Can search for tags and preview works properly', async ({ page }) => {
await createNotebookEntryAndTags(page); await createNotebookEntryAndTags(page);
await page.locator('[aria-label="OpenMCT Search"] input[type="search"]').click(); await page.getByRole('search').getByLabel('Search Input').click();
await page.locator('[aria-label="OpenMCT Search"] input[type="search"]').fill('sc'); await page.getByRole('search').getByLabel('Search Input').fill('sc');
await expect(page.locator('[aria-label="Search Result"]')).toContainText('Science'); await expect(page.getByRole('listitem', { name: 'Annotation Search Result' })).toContainText(
await expect(page.locator('[aria-label="Search Result"]')).not.toContainText('Driving'); 'Science'
);
await expect(
page.getByRole('listitem', { name: 'Annotation Search Result' })
).not.toContainText('Driving');
await page.locator('[aria-label="OpenMCT Search"] input[type="search"]').click(); await page.getByRole('search').getByLabel('Search Input').click();
await page.locator('[aria-label="OpenMCT Search"] input[type="search"]').fill('Sc'); await page.getByRole('search').getByLabel('Search Input').fill('Sc');
await expect(page.locator('[aria-label="Search Result"]')).toContainText('Science'); await expect(page.getByRole('listitem', { name: 'Annotation Search Result' })).toContainText(
await expect(page.locator('[aria-label="Search Result"]')).not.toContainText('Driving'); 'Science'
);
await expect(
page.getByRole('listitem', { name: 'Annotation Search Result' })
).not.toContainText('Driving');
await page.locator('[aria-label="OpenMCT Search"] input[type="search"]').click(); await page.getByRole('search').getByLabel('Search Input').click();
await page.locator('[aria-label="OpenMCT Search"] input[type="search"]').fill('Xq'); await page.getByRole('search').getByLabel('Search Input').fill('Xq');
await expect(page.locator('text=No results found')).toBeVisible(); await expect(page.getByText('No results found')).toBeVisible();
await createDomainObjectWithDefaults(page, { await createDomainObjectWithDefaults(page, {
type: 'Display Layout' type: 'Display Layout'
}); });
// Go back into edit mode for the display layout // Go back into edit mode for the display layout
await page.locator('button[title="Edit"]').click(); await page.getByRole('button', { name: 'Edit Object' }).click();
await page.locator('[aria-label="OpenMCT Search"] input[type="search"]').click(); await page.getByRole('search').getByLabel('Search Input').click();
await page.locator('[aria-label="OpenMCT Search"] input[type="search"]').fill('Sc'); await page.getByRole('search').getByLabel('Search Input').fill('Sc');
await expect(page.locator('[aria-label="Search Result"]')).toContainText('Science'); await expect(page.getByRole('listitem', { name: 'Annotation Search Result' })).toContainText(
'Science'
);
await page.getByText('Entry 0').click(); await page.getByText('Entry 0').click();
await expect(page.locator('.js-preview-window')).toBeVisible(); await expect(page.locator('.js-preview-window')).toBeVisible();
}); });
@ -138,8 +148,10 @@ test.describe('Tagging in Notebooks @addInit', () => {
await expect(page.locator('[aria-label="Tags Inspector"]')).toContainText('Science'); await expect(page.locator('[aria-label="Tags Inspector"]')).toContainText('Science');
await expect(page.locator('[aria-label="Tags Inspector"]')).not.toContainText('Driving'); await expect(page.locator('[aria-label="Tags Inspector"]')).not.toContainText('Driving');
await page.locator('[aria-label="OpenMCT Search"] input[type="search"]').fill('sc'); await page.getByRole('search').getByLabel('Search Input').fill('sc');
await expect(page.locator('[aria-label="Search Result"]')).not.toContainText('Driving'); await expect(
page.getByRole('listitem', { name: 'Annotation Search Result' })
).not.toContainText('Driving');
}); });
test('Can delete entries without tags', async ({ page }) => { test('Can delete entries without tags', async ({ page }) => {
@ -167,17 +179,17 @@ test.describe('Tagging in Notebooks @addInit', () => {
test('Can delete objects with tags and neither return in search', async ({ page }) => { test('Can delete objects with tags and neither return in search', async ({ page }) => {
await createNotebookEntryAndTags(page); await createNotebookEntryAndTags(page);
// Delete Notebook // Delete Notebook
await page.locator('button[title="More options"]').click(); await page.locator('button[title="More actions"]').click();
await page.locator('li[title="Remove this object from its containing object."]').click(); await page.locator('li[title="Remove this object from its containing object."]').click();
await page.locator('button:has-text("OK")').click(); await page.locator('button:has-text("OK")').click();
await page.goto('./', { waitUntil: 'domcontentloaded' }); await page.goto('./', { waitUntil: 'domcontentloaded' });
await page.locator('[aria-label="OpenMCT Search"] input[type="search"]').fill('Unnamed'); await page.getByRole('search').getByLabel('Search Input').fill('Unnamed');
await expect(page.locator('text=No results found')).toBeVisible(); await expect(page.getByText('No results found')).toBeVisible();
await page.locator('[aria-label="OpenMCT Search"] input[type="search"]').fill('sci'); await page.getByRole('search').getByLabel('Search Input').fill('sci');
await expect(page.locator('text=No results found')).toBeVisible(); await expect(page.getByText('No results found')).toBeVisible();
await page.locator('[aria-label="OpenMCT Search"] input[type="search"]').fill('dri'); await page.getByRole('search').getByLabel('Search Input').fill('dri');
await expect(page.locator('text=No results found')).toBeVisible(); await expect(page.getByText('No results found')).toBeVisible();
}); });
test('Tags persist across reload', async ({ page }) => { test('Tags persist across reload', async ({ page }) => {
//Go to baseURL //Go to baseURL

View File

@ -1,5 +1,5 @@
/***************************************************************************** /*****************************************************************************
* Open MCT, Copyright (c) 2014-2023, United States Government * Open MCT, Copyright (c) 2014-2024, United States Government
* as represented by the Administrator of the National Aeronautics and Space * as represented by the Administrator of the National Aeronautics and Space
* Administration. All rights reserved. * Administration. All rights reserved.
* *
@ -24,9 +24,9 @@
This test suite is dedicated to tests which verify the basic operations surrounding Notebooks with CouchDB. This test suite is dedicated to tests which verify the basic operations surrounding Notebooks with CouchDB.
*/ */
const { test, expect } = require('../../../../pluginFixtures'); import { createDomainObjectWithDefaults } from '../../../../appActions.js';
const { createDomainObjectWithDefaults } = require('../../../../appActions'); import * as nbUtils from '../../../../helper/notebookUtils.js';
const nbUtils = require('../../../../helper/notebookUtils'); import { expect, test } from '../../../../pluginFixtures.js';
test.describe('Notebook Tests with CouchDB @couchdb', () => { test.describe('Notebook Tests with CouchDB @couchdb', () => {
let testNotebook; let testNotebook;
@ -185,16 +185,20 @@ test.describe('Notebook Tests with CouchDB @couchdb', () => {
await page.locator('[aria-label="OpenMCT Search"] input[type="search"]').click(); await page.locator('[aria-label="OpenMCT Search"] input[type="search"]').click();
//Partial match for "Science" should only return Science //Partial match for "Science" should only return Science
await page.locator('[aria-label="OpenMCT Search"] input[type="search"]').fill('Sc'); await page.locator('[aria-label="OpenMCT Search"] input[type="search"]').fill('Sc');
await expect(page.locator('[aria-label="Search Result"]').first()).toContainText('Science'); await expect(page.locator('[aria-label="Annotation Search Result"]').first()).toContainText(
await expect(page.locator('[aria-label="Search Result"]').first()).not.toContainText('Driving'); 'Science'
await expect(page.locator('[aria-label="Search Result"]').first()).not.toContainText( );
await expect(page.locator('[aria-label="Annotation Search Result"]').first()).not.toContainText(
'Driving'
);
await expect(page.locator('[aria-label="Annotation Search Result"]').first()).not.toContainText(
'Drilling' 'Drilling'
); );
//Searching for a tag which does not exist should return an empty result //Searching for a tag which does not exist should return an empty result
await page.locator('[aria-label="OpenMCT Search"] input[type="search"]').click(); await page.locator('[aria-label="OpenMCT Search"] input[type="search"]').click();
await page.locator('[aria-label="OpenMCT Search"] input[type="search"]').fill('Xq'); await page.locator('[aria-label="OpenMCT Search"] input[type="search"]').fill('Xq');
await expect(page.locator('text=No results found')).toBeVisible(); await expect(page.getByText('No results found')).toBeVisible();
}); });
}); });

View File

@ -1,5 +1,5 @@
/***************************************************************************** /*****************************************************************************
* Open MCT, Copyright (c) 2014-2023, United States Government * Open MCT, Copyright (c) 2014-2024, United States Government
* as represented by the Administrator of the National Aeronautics and Space * as represented by the Administrator of the National Aeronautics and Space
* Administration. All rights reserved. * Administration. All rights reserved.
* *
@ -20,14 +20,14 @@
* at runtime from the About dialog for additional information. * at runtime from the About dialog for additional information.
*****************************************************************************/ *****************************************************************************/
const { test, expect, streamToString } = require('../../../../pluginFixtures'); import { openObjectTreeContextMenu } from '../../../../appActions.js';
const { openObjectTreeContextMenu } = require('../../../../appActions'); import {
const {
lockPage,
dragAndDropEmbed, dragAndDropEmbed,
enterTextEntry, enterTextEntry,
lockPage,
startAndAddRestrictedNotebookObject startAndAddRestrictedNotebookObject
} = require('../../../../helper/notebookUtils'); } from '../../../../helper/notebookUtils.js';
import { expect, streamToString, test } from '../../../../pluginFixtures.js';
const TEST_TEXT = 'Testing text for entries.'; const TEST_TEXT = 'Testing text for entries.';
const TEST_TEXT_NAME = 'Test Page'; const TEST_TEXT_NAME = 'Test Page';
@ -152,18 +152,18 @@ test.describe('Restricted Notebook with a page locked and with an embed @addInit
test('Allows embeds to be deleted if page unlocked @addInit', async ({ page }) => { test('Allows embeds to be deleted if page unlocked @addInit', async ({ page }) => {
// Click embed popup menu // Click embed popup menu
await page.locator('.c-ne__embed__name .c-icon-button').click(); await page.getByLabel('Notebook Entry').getByLabel('More actions').click();
const embedMenu = page.locator('body >> .c-menu'); const embedMenu = page.getByLabel('Super Menu');
await expect(embedMenu).toContainText('Remove This Embed'); await expect(embedMenu).toContainText('Remove This Embed');
}); });
test('Disallows embeds to be deleted if page locked @addInit', async ({ page }) => { test('Disallows embeds to be deleted if page locked @addInit', async ({ page }) => {
await lockPage(page); await lockPage(page);
// Click embed popup menu // Click embed popup menu
await page.locator('.c-ne__embed__name .c-icon-button').click(); await page.getByLabel('Notebook Entry').getByLabel('More actions').click();
const embedMenu = page.locator('body >> .c-menu'); const embedMenu = page.getByLabel('Super Menu');
await expect(embedMenu).not.toContainText('Remove This Embed'); await expect(embedMenu).not.toContainText('Remove This Embed');
}); });
}); });
@ -176,7 +176,7 @@ test.describe('can export restricted notebook as text', () => {
test('basic functionality ', async ({ page }) => { test('basic functionality ', async ({ page }) => {
await enterTextEntry(page, `Foo bar entry`); await enterTextEntry(page, `Foo bar entry`);
// Click on 3 Dot Menu // Click on 3 Dot Menu
await page.locator('button[title="More options"]').click(); await page.locator('button[title="More actions"]').click();
const downloadPromise = page.waitForEvent('download'); const downloadPromise = page.waitForEvent('download');
await page.getByRole('menuitem', { name: /Export Notebook as Text/ }).click(); await page.getByRole('menuitem', { name: /Export Notebook as Text/ }).click();

View File

@ -1,5 +1,5 @@
/***************************************************************************** /*****************************************************************************
* Open MCT, Copyright (c) 2014-2023, United States Government * Open MCT, Copyright (c) 2014-2024, United States Government
* as represented by the Administrator of the National Aeronautics and Space * as represented by the Administrator of the National Aeronautics and Space
* Administration. All rights reserved. * Administration. All rights reserved.
* *
@ -19,13 +19,13 @@
* this source code distribution or the Licensing information page available * this source code distribution or the Licensing information page available
* at runtime from the About dialog for additional information. * at runtime from the About dialog for additional information.
*****************************************************************************/ *****************************************************************************/
/* global __dirname */
/* /*
* This test suite is dedicated to testing the operator status plugin. * This test suite is dedicated to testing the operator status plugin.
*/ */
const path = require('path'); import { fileURLToPath } from 'url';
const { test, expect } = require('../../../../pluginFixtures');
import { expect, test } from '../../../../pluginFixtures.js';
/* /*
@ -41,17 +41,17 @@ test.describe('Operator Status', () => {
test.beforeEach(async ({ page }) => { test.beforeEach(async ({ page }) => {
// FIXME: determine if plugins will be added to index.html or need to be injected // FIXME: determine if plugins will be added to index.html or need to be injected
await page.addInitScript({ await page.addInitScript({
path: path.join(__dirname, '../../../../helper/', 'addInitExampleUser.js') path: fileURLToPath(new URL('../../../../helper/addInitExampleUser.js', import.meta.url))
}); });
await page.addInitScript({ await page.addInitScript({
path: path.join(__dirname, '../../../../helper/', 'addInitOperatorStatus.js') path: fileURLToPath(new URL('../../../../helper/addInitOperatorStatus.js', import.meta.url))
}); });
await page.goto('./', { waitUntil: 'domcontentloaded' }); await page.goto('./', { waitUntil: 'domcontentloaded' });
await expect(page.getByText('Select Role')).toBeVisible(); await expect(page.getByText('Select Role')).toBeVisible();
// Description should be empty https://github.com/nasa/openmct/issues/6978 // Description should be empty https://github.com/nasa/openmct/issues/6978
await expect(page.locator('.c-message__action-text')).toBeHidden(); await expect(page.locator('.c-message__action-text')).toBeHidden();
// set role // set role
await page.getByRole('button', { name: 'Select' }).click(); await page.getByRole('button', { name: 'Select', exact: true }).click();
// dismiss role confirmation popup // dismiss role confirmation popup
await page.getByRole('button', { name: 'Dismiss' }).click(); await page.getByRole('button', { name: 'Dismiss' }).click();
}); });

View File

@ -1,5 +1,5 @@
/***************************************************************************** /*****************************************************************************
* Open MCT, Copyright (c) 2014-2023, United States Government * Open MCT, Copyright (c) 2014-2024, United States Government
* as represented by the Administrator of the National Aeronautics and Space * as represented by the Administrator of the National Aeronautics and Space
* Administration. All rights reserved. * Administration. All rights reserved.
* *
@ -24,8 +24,8 @@
Testsuite for plot autoscale. Testsuite for plot autoscale.
*/ */
const { createDomainObjectWithDefaults } = require('../../../../appActions'); import { createDomainObjectWithDefaults } from '../../../../appActions.js';
const { test, expect } = require('../../../../pluginFixtures'); import { expect, test } from '../../../../pluginFixtures.js';
test.use({ test.use({
viewport: { viewport: {
width: 1280, width: 1280,
@ -58,7 +58,7 @@ test.describe('Autoscale', () => {
await testYTicks(page, ['-1.00', '-0.50', '0.00', '0.50', '1.00']); await testYTicks(page, ['-1.00', '-0.50', '0.00', '0.50', '1.00']);
// enter edit mode // enter edit mode
await page.click('button[title="Edit"]'); await page.getByLabel('Edit Object').click();
await page.getByRole('tab', { name: 'Config' }).click(); await page.getByRole('tab', { name: 'Config' }).click();
await turnOffAutoscale(page); await turnOffAutoscale(page);

Binary file not shown.

Before

Width:  |  Height:  |  Size: 19 KiB

After

Width:  |  Height:  |  Size: 30 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 20 KiB

After

Width:  |  Height:  |  Size: 39 KiB

Some files were not shown because too many files have changed in this diff Show More