chore: add prettier (2/3): apply formatting, re-enable lint ci step (#6682)

* style: apply prettier formatting

* fix: re-enable lint ci check
This commit is contained in:
Jesse Mazzella 2023-05-18 14:54:46 -07:00 committed by GitHub
parent 172e0b23fd
commit caa7bc6fae
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
976 changed files with 115922 additions and 114693 deletions

View File

@ -13,12 +13,12 @@ executors:
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,19 +30,19 @@ 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
steps: steps:
- when: - when:
condition: condition:
equal: [false, << pipeline.parameters.BUST_CACHE >> ] equal: [false, << pipeline.parameters.BUST_CACHE >>]
steps: steps:
- 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,13 +64,13 @@ 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
steps: steps:
- run: npm run cov:e2e:report || true - run: npm run cov:e2e:report || true
- run: npm run cov:e2e:<<parameters.suite>>:publish - run: npm run cov:e2e:<<parameters.suite>>:publish
orbs: orbs:
node: circleci/node@5.1.0 node: circleci/node@5.1.0
browser-tools: circleci/browser-tools@1.3.0 browser-tools: circleci/browser-tools@1.3.0
@ -115,7 +115,7 @@ jobs:
path: coverage path: coverage
- when: - when:
condition: 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 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-test: e2e-test:
@ -131,16 +131,16 @@ jobs:
node-version: <<parameters.node-version>> node-version: <<parameters.node-version>>
- 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: SHARD="$((${CIRCLE_NODE_INDEX}+1))"; npm run test:e2e:<<parameters.suite>> -- --shard=${SHARD}/${CIRCLE_NODE_TOTAL}
- 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
steps: steps:
- generate_e2e_code_cov_report: - generate_e2e_code_cov_report:
suite: <<parameters.suite>> suite: <<parameters.suite>>
- store_test_results: - store_test_results:
path: test-results/results.xml path: test-results/results.xml
- store_artifacts: - store_artifacts:
@ -151,7 +151,7 @@ jobs:
path: html-test-results path: html-test-results
- when: - when:
condition: 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 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-couchdb: e2e-couchdb:
@ -172,10 +172,10 @@ jobs:
- run: npm run test:e2e:couchdb - run: npm run test:e2e:couchdb
- 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
steps: steps:
- generate_e2e_code_cov_report: - generate_e2e_code_cov_report:
suite: full #add to full suite suite: full #add to full suite
- store_test_results: - store_test_results:
path: test-results/results.xml path: test-results/results.xml
- store_artifacts: - store_artifacts:
@ -186,7 +186,7 @@ jobs:
path: html-test-results path: html-test-results
- when: - when:
condition: 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 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
perf-test: perf-test:
@ -206,7 +206,7 @@ jobs:
path: html-test-results path: html-test-results
- 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
steps: steps:
- generate_and_store_version_and_filesystem_artifacts - generate_and_store_version_and_filesystem_artifacts
visual-test: visual-test:
@ -226,15 +226,15 @@ jobs:
path: html-test-results path: html-test-results
- when: - when:
condition: 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 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:
# - lint: - lint:
# name: node16-lint name: node16-lint
# node-version: lts/gallium node-version: lts/gallium
- unit-test: - unit-test:
name: node18-chrome name: node18-chrome
node-version: lts/hydrogen node-version: lts/hydrogen
@ -269,7 +269,7 @@ workflows:
node-version: lts/hydrogen node-version: lts/hydrogen
triggers: triggers:
- schedule: - schedule:
cron: "0 0 * * *" cron: '0 0 * * *'
filters: filters:
branches: branches:
only: only:

View File

@ -1,166 +1,166 @@
const LEGACY_FILES = ["example/**"]; const LEGACY_FILES = ['example/**'];
module.exports = { module.exports = {
"env": { env: {
"browser": true, browser: true,
"es6": true, es6: true,
"jasmine": true, jasmine: true,
"amd": true amd: true
}, },
"globals": { globals: {
"_": "readonly" _: 'readonly'
}, },
"plugins": ["prettier"], plugins: ['prettier'],
"extends": [ extends: [
"eslint:recommended", 'eslint:recommended',
"plugin:compat/recommended", 'plugin:compat/recommended',
"plugin:vue/recommended", 'plugin:vue/recommended',
"plugin:you-dont-need-lodash-underscore/compatible", 'plugin:you-dont-need-lodash-underscore/compatible',
"plugin:prettier/recommended" 'plugin:prettier/recommended'
],
parser: 'vue-eslint-parser',
parserOptions: {
parser: '@babel/eslint-parser',
requireConfigFile: false,
allowImportExportEverywhere: true,
ecmaVersion: 2015,
ecmaFeatures: {
impliedStrict: true
}
},
rules: {
'prettier/prettier': 'error',
'you-dont-need-lodash-underscore/omit': 'off',
'you-dont-need-lodash-underscore/throttle': 'off',
'you-dont-need-lodash-underscore/flatten': 'off',
'you-dont-need-lodash-underscore/get': 'off',
'no-bitwise': 'error',
curly: 'error',
eqeqeq: 'error',
'guard-for-in': 'error',
'no-extend-native': 'error',
'no-inner-declarations': 'off',
'no-use-before-define': ['error', 'nofunc'],
'no-caller': 'error',
'no-irregular-whitespace': 'error',
'no-new': 'error',
'no-shadow': 'error',
'no-undef': 'error',
'no-unused-vars': [
'error',
{
vars: 'all',
args: 'none'
}
], ],
"parser": "vue-eslint-parser", 'no-console': 'off',
"parserOptions": { 'new-cap': [
"parser": "@babel/eslint-parser", 'error',
"requireConfigFile": false, {
"allowImportExportEverywhere": true, capIsNew: false,
"ecmaVersion": 2015, properties: false
"ecmaFeatures": { }
"impliedStrict": true ],
} 'dot-notation': 'error',
},
"rules": {
"prettier/prettier": "error",
"you-dont-need-lodash-underscore/omit": "off",
"you-dont-need-lodash-underscore/throttle": "off",
"you-dont-need-lodash-underscore/flatten": "off",
"you-dont-need-lodash-underscore/get": "off",
"no-bitwise": "error",
"curly": "error",
"eqeqeq": "error",
"guard-for-in": "error",
"no-extend-native": "error",
"no-inner-declarations": "off",
"no-use-before-define": ["error", "nofunc"],
"no-caller": "error",
"no-irregular-whitespace": "error",
"no-new": "error",
"no-shadow": "error",
"no-undef": "error",
"no-unused-vars": [
"error",
{
"vars": "all",
"args": "none"
}
],
"no-console": "off",
"new-cap": [
"error",
{
"capIsNew": false,
"properties": false
}
],
"dot-notation": "error",
// https://eslint.org/docs/rules/no-case-declarations // https://eslint.org/docs/rules/no-case-declarations
"no-case-declarations": "error", 'no-case-declarations': 'error',
// https://eslint.org/docs/rules/max-classes-per-file // https://eslint.org/docs/rules/max-classes-per-file
"max-classes-per-file": ["error", 1], 'max-classes-per-file': ['error', 1],
// https://eslint.org/docs/rules/no-eq-null // https://eslint.org/docs/rules/no-eq-null
"no-eq-null": "error", 'no-eq-null': 'error',
// https://eslint.org/docs/rules/no-eval // https://eslint.org/docs/rules/no-eval
"no-eval": "error", 'no-eval': 'error',
// https://eslint.org/docs/rules/no-implicit-globals // https://eslint.org/docs/rules/no-implicit-globals
"no-implicit-globals": "error", 'no-implicit-globals': 'error',
// https://eslint.org/docs/rules/no-implied-eval // https://eslint.org/docs/rules/no-implied-eval
"no-implied-eval": "error", 'no-implied-eval': 'error',
// https://eslint.org/docs/rules/no-lone-blocks // https://eslint.org/docs/rules/no-lone-blocks
"no-lone-blocks": "error", 'no-lone-blocks': 'error',
// https://eslint.org/docs/rules/no-loop-func // https://eslint.org/docs/rules/no-loop-func
"no-loop-func": "error", 'no-loop-func': 'error',
// https://eslint.org/docs/rules/no-new-func // https://eslint.org/docs/rules/no-new-func
"no-new-func": "error", 'no-new-func': 'error',
// https://eslint.org/docs/rules/no-new-wrappers // https://eslint.org/docs/rules/no-new-wrappers
"no-new-wrappers": "error", 'no-new-wrappers': 'error',
// https://eslint.org/docs/rules/no-octal-escape // https://eslint.org/docs/rules/no-octal-escape
"no-octal-escape": "error", 'no-octal-escape': 'error',
// https://eslint.org/docs/rules/no-proto // https://eslint.org/docs/rules/no-proto
"no-proto": "error", 'no-proto': 'error',
// https://eslint.org/docs/rules/no-return-await // https://eslint.org/docs/rules/no-return-await
"no-return-await": "error", 'no-return-await': 'error',
// https://eslint.org/docs/rules/no-script-url // https://eslint.org/docs/rules/no-script-url
"no-script-url": "error", 'no-script-url': 'error',
// https://eslint.org/docs/rules/no-self-compare // https://eslint.org/docs/rules/no-self-compare
"no-self-compare": "error", 'no-self-compare': 'error',
// https://eslint.org/docs/rules/no-sequences // https://eslint.org/docs/rules/no-sequences
"no-sequences": "error", 'no-sequences': 'error',
// https://eslint.org/docs/rules/no-unmodified-loop-condition // https://eslint.org/docs/rules/no-unmodified-loop-condition
"no-unmodified-loop-condition": "error", 'no-unmodified-loop-condition': 'error',
// https://eslint.org/docs/rules/no-useless-call // https://eslint.org/docs/rules/no-useless-call
"no-useless-call": "error", 'no-useless-call': 'error',
// https://eslint.org/docs/rules/no-nested-ternary // https://eslint.org/docs/rules/no-nested-ternary
"no-nested-ternary": "error", 'no-nested-ternary': 'error',
// https://eslint.org/docs/rules/no-useless-computed-key // https://eslint.org/docs/rules/no-useless-computed-key
"no-useless-computed-key": "error", 'no-useless-computed-key': 'error',
// https://eslint.org/docs/rules/no-var // https://eslint.org/docs/rules/no-var
"no-var": "error", 'no-var': 'error',
// https://eslint.org/docs/rules/one-var // https://eslint.org/docs/rules/one-var
"one-var": ["error", "never"], 'one-var': ['error', 'never'],
// https://eslint.org/docs/rules/default-case-last // https://eslint.org/docs/rules/default-case-last
"default-case-last": "error", 'default-case-last': 'error',
// https://eslint.org/docs/rules/default-param-last // https://eslint.org/docs/rules/default-param-last
"default-param-last": "error", 'default-param-last': 'error',
// https://eslint.org/docs/rules/grouped-accessor-pairs // https://eslint.org/docs/rules/grouped-accessor-pairs
"grouped-accessor-pairs": "error", 'grouped-accessor-pairs': 'error',
// https://eslint.org/docs/rules/no-constructor-return // https://eslint.org/docs/rules/no-constructor-return
"no-constructor-return": "error", 'no-constructor-return': 'error',
// https://eslint.org/docs/rules/array-callback-return // https://eslint.org/docs/rules/array-callback-return
"array-callback-return": "error", 'array-callback-return': 'error',
// https://eslint.org/docs/rules/no-invalid-this // https://eslint.org/docs/rules/no-invalid-this
"no-invalid-this": "error", // Believe this one actually surfaces some bugs 'no-invalid-this': 'error', // Believe this one actually surfaces some bugs
// https://eslint.org/docs/rules/func-style // https://eslint.org/docs/rules/func-style
"func-style": ["error", "declaration"], 'func-style': ['error', 'declaration'],
// https://eslint.org/docs/rules/no-unused-expressions // https://eslint.org/docs/rules/no-unused-expressions
"no-unused-expressions": "error", 'no-unused-expressions': 'error',
// https://eslint.org/docs/rules/no-useless-concat // https://eslint.org/docs/rules/no-useless-concat
"no-useless-concat": "error", 'no-useless-concat': 'error',
// https://eslint.org/docs/rules/radix // https://eslint.org/docs/rules/radix
"radix": "error", radix: 'error',
// https://eslint.org/docs/rules/require-await // https://eslint.org/docs/rules/require-await
"require-await": "error", 'require-await': 'error',
// https://eslint.org/docs/rules/no-alert // https://eslint.org/docs/rules/no-alert
"no-alert": "error", 'no-alert': 'error',
// https://eslint.org/docs/rules/no-useless-constructor // https://eslint.org/docs/rules/no-useless-constructor
"no-useless-constructor": "error", 'no-useless-constructor': 'error',
// https://eslint.org/docs/rules/no-duplicate-imports // https://eslint.org/docs/rules/no-duplicate-imports
"no-duplicate-imports": "error", 'no-duplicate-imports': 'error',
// https://eslint.org/docs/rules/no-implicit-coercion // https://eslint.org/docs/rules/no-implicit-coercion
"no-implicit-coercion": "error", 'no-implicit-coercion': 'error',
//https://eslint.org/docs/rules/no-unneeded-ternary //https://eslint.org/docs/rules/no-unneeded-ternary
"no-unneeded-ternary": "error", 'no-unneeded-ternary': 'error',
"vue/first-attribute-linebreak": "error", 'vue/first-attribute-linebreak': 'error',
"vue/multiline-html-element-content-newline": "off", 'vue/multiline-html-element-content-newline': 'off',
"vue/singleline-html-element-content-newline": "off", 'vue/singleline-html-element-content-newline': 'off',
"vue/multi-word-component-names": "off", // TODO enable, align with conventions 'vue/multi-word-component-names': 'off', // TODO enable, align with conventions
"vue/no-mutating-props": "off" 'vue/no-mutating-props': 'off'
}, },
"overrides": [ overrides: [
{ {
"files": LEGACY_FILES, files: LEGACY_FILES,
"rules": { rules: {
"no-unused-vars": [ 'no-unused-vars': [
"warn", 'warn',
{ {
"vars": "all", vars: 'all',
"args": "none", args: 'none',
"varsIgnorePattern": "controller" varsIgnorePattern: 'controller'
} }
], ],
"no-nested-ternary": "off", 'no-nested-ternary': 'off',
"no-var": "off", 'no-var': 'off',
"one-var": "off" 'one-var': 'off'
} }
} }
] ]
}; };

View File

@ -1,39 +1,38 @@
version: 2 version: 2
updates: updates:
- package-ecosystem: "npm" - package-ecosystem: 'npm'
directory: "/" directory: '/'
schedule: schedule:
interval: "weekly" interval: 'weekly'
open-pull-requests-limit: 10 open-pull-requests-limit: 10
labels: labels:
- "pr:daveit" - 'pr:daveit'
- "pr:e2e" - 'pr:e2e'
- "type:maintenance" - 'type:maintenance'
- "dependencies" - 'dependencies'
- "pr:platform" - 'pr:platform'
ignore: ignore:
#We have to source the playwright container which is not detected by Dependabot #We have to source the playwright container which is not detected by Dependabot
- dependency-name: "@playwright/test" - dependency-name: '@playwright/test'
- dependency-name: "playwright-core" - dependency-name: 'playwright-core'
#Lots of noise in these type patch releases. #Lots of noise in these type patch releases.
- dependency-name: "@babel/eslint-parser" - dependency-name: '@babel/eslint-parser'
update-types: ["version-update:semver-patch"] update-types: ['version-update:semver-patch']
- dependency-name: "eslint-plugin-vue" - dependency-name: 'eslint-plugin-vue'
update-types: ["version-update:semver-patch"] update-types: ['version-update:semver-patch']
- dependency-name: "babel-loader" - dependency-name: 'babel-loader'
update-types: ["version-update:semver-patch"] update-types: ['version-update:semver-patch']
- dependency-name: "sinon" - dependency-name: 'sinon'
update-types: ["version-update:semver-patch"] update-types: ['version-update:semver-patch']
- dependency-name: "moment-timezone" - dependency-name: 'moment-timezone'
update-types: ["version-update:semver-patch"] update-types: ['version-update:semver-patch']
- dependency-name: "@types/lodash" - dependency-name: '@types/lodash'
update-types: ["version-update:semver-patch"] update-types: ['version-update:semver-patch']
- package-ecosystem: "github-actions" - package-ecosystem: 'github-actions'
directory: "/" directory: '/'
schedule: schedule:
interval: "daily" interval: 'daily'
labels: labels:
- "pr:daveit" - 'pr:daveit'
- "type:maintenance" - 'type:maintenance'
- "dependencies" - 'dependencies'

View File

@ -1,4 +1,4 @@
name: "e2e-couchdb" name: 'e2e-couchdb'
on: on:
workflow_dispatch: workflow_dispatch:
pull_request: pull_request:
@ -17,7 +17,7 @@ jobs:
- run: npx playwright@1.32.3 install - run: npx playwright@1.32.3 install
- run: npm install - run: npm install
- name: Start CouchDB Docker Container and Init with Setup Scripts - name: Start CouchDB Docker Container and Init with Setup Scripts
run : | run: |
export $(cat src/plugins/persistence/couch/.env.ci | xargs) export $(cat src/plugins/persistence/couch/.env.ci | xargs)
docker-compose -f src/plugins/persistence/couch/couchdb-compose.yaml up --detach docker-compose -f src/plugins/persistence/couch/couchdb-compose.yaml up --detach
sleep 3 sleep 3

View File

@ -1,4 +1,4 @@
name: "e2e-pr" name: 'e2e-pr'
on: on:
workflow_dispatch: workflow_dispatch:
pull_request: pull_request:

View File

@ -1,8 +1,8 @@
name: "pr-platform" name: 'pr-platform'
on: on:
workflow_dispatch: workflow_dispatch:
pull_request: pull_request:
types: [ labeled ] types: [labeled]
jobs: jobs:
e2e-full: e2e-full:

View File

@ -22,5 +22,5 @@ jobs:
- name: Linting Pull Request - name: Linting Pull Request
uses: makaroni4/prcop@v1.0.35 uses: makaroni4/prcop@v1.0.35
with: with:
config-file: ".github/workflows/prcop-config.json" config-file: '.github/workflows/prcop-config.json'
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

View File

@ -8,169 +8,156 @@ 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"); const path = require('path');
const packageDefinition = require("../package.json"); const packageDefinition = require('../package.json');
const CopyWebpackPlugin = require("copy-webpack-plugin"); const CopyWebpackPlugin = require('copy-webpack-plugin');
const webpack = require("webpack"); const webpack = require('webpack');
const MiniCssExtractPlugin = require("mini-css-extract-plugin"); const MiniCssExtractPlugin = require('mini-css-extract-plugin');
const { VueLoaderPlugin } = require("vue-loader"); const { VueLoaderPlugin } = require('vue-loader');
let gitRevision = "error-retrieving-revision"; let gitRevision = 'error-retrieving-revision';
let gitBranch = "error-retrieving-branch"; let gitBranch = 'error-retrieving-branch';
try { try {
gitRevision = require("child_process") gitRevision = require('child_process').execSync('git rev-parse HEAD').toString().trim();
.execSync("git rev-parse HEAD") gitBranch = require('child_process')
.toString() .execSync('git rev-parse --abbrev-ref HEAD')
.trim(); .toString()
gitBranch = require("child_process") .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 = path.resolve(__dirname, '..');
/** @type {import('webpack').Configuration} */ /** @type {import('webpack').Configuration} */
const config = { const config = {
context: projectRootDir, context: projectRootDir,
entry: { entry: {
openmct: "./openmct.js", openmct: './openmct.js',
generatorWorker: "./example/generator/generatorWorker.js", generatorWorker: './example/generator/generatorWorker.js',
couchDBChangesFeed: couchDBChangesFeed: './src/plugins/persistence/couch/CouchChangesFeed.js',
"./src/plugins/persistence/couch/CouchChangesFeed.js", inMemorySearchWorker: './src/api/objects/InMemorySearchWorker.js',
inMemorySearchWorker: "./src/api/objects/InMemorySearchWorker.js", espressoTheme: './src/plugins/themes/espresso-theme.scss',
espressoTheme: "./src/plugins/themes/espresso-theme.scss", snowTheme: './src/plugins/themes/snow-theme.scss'
snowTheme: "./src/plugins/themes/snow-theme.scss" },
}, output: {
output: { globalObject: 'this',
globalObject: "this", filename: '[name].js',
filename: "[name].js", path: path.resolve(projectRootDir, 'dist'),
path: path.resolve(projectRootDir, "dist"), library: 'openmct',
library: "openmct", libraryTarget: 'umd',
libraryTarget: "umd", publicPath: '',
publicPath: "", hashFunction: 'xxhash64',
hashFunction: "xxhash64", clean: true
clean: true },
}, resolve: {
resolve: { 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',
saveAs: "file-saver/src/FileSaver.js", csv: 'comma-separated-values',
csv: "comma-separated-values", EventEmitter: 'eventemitter3',
EventEmitter: "eventemitter3", bourbon: 'bourbon.scss',
bourbon: "bourbon.scss", 'plotly-basic': 'plotly.js-basic-dist',
"plotly-basic": "plotly.js-basic-dist", 'plotly-gl2d': 'plotly.js-gl2d-dist',
"plotly-gl2d": "plotly.js-gl2d-dist", 'd3-scale': path.join(projectRootDir, 'node_modules/d3-scale/dist/d3-scale.min.js'),
"d3-scale": path.join( printj: path.join(projectRootDir, 'node_modules/printj/dist/printj.min.js'),
projectRootDir, styles: path.join(projectRootDir, 'src/styles'),
"node_modules/d3-scale/dist/d3-scale.min.js" MCT: path.join(projectRootDir, 'src/MCT'),
), testUtils: path.join(projectRootDir, 'src/utils/testUtils.js'),
printj: path.join( objectUtils: path.join(projectRootDir, 'src/api/objects/object-utils.js'),
projectRootDir, kdbush: path.join(projectRootDir, 'node_modules/kdbush/kdbush.min.js'),
"node_modules/printj/dist/printj.min.js" utils: path.join(projectRootDir, 'src/utils')
),
styles: path.join(projectRootDir, "src/styles"),
MCT: path.join(projectRootDir, "src/MCT"),
testUtils: path.join(projectRootDir, "src/utils/testUtils.js"),
objectUtils: path.join(
projectRootDir,
"src/api/objects/object-utils.js"
),
"kdbush": path.join(projectRootDir, "node_modules/kdbush/kdbush.min.js"),
utils: path.join(projectRootDir, "src/utils")
}
},
plugins: [
new webpack.DefinePlugin({
__OPENMCT_VERSION__: `'${packageDefinition.version}'`,
__OPENMCT_BUILD_DATE__: `'${new Date()}'`,
__OPENMCT_REVISION__: `'${gitRevision}'`,
__OPENMCT_BUILD_BRANCH__: `'${gitBranch}'`
}),
new VueLoaderPlugin(),
new CopyWebpackPlugin({
patterns: [
{
from: "src/images/favicons",
to: "favicons"
},
{
from: "./index.html",
transform: function (content) {
return content.toString().replace(/dist\//g, "");
}
},
{
from: "src/plugins/imagery/layers",
to: "imagery"
}
]
}),
new MiniCssExtractPlugin({
filename: "[name].css",
chunkFilename: "[name].css"
})
],
module: {
rules: [
{
test: /\.(sc|sa|c)ss$/,
use: [
MiniCssExtractPlugin.loader,
{
loader: "css-loader"
},
{
loader: "resolve-url-loader"
},
{
loader: "sass-loader",
options: { sourceMap: true }
}
]
},
{
test: /\.vue$/,
use: "vue-loader"
},
{
test: /\.html$/,
type: "asset/source"
},
{
test: /\.(jpg|jpeg|png|svg)$/,
type: "asset/resource",
generator: {
filename: "images/[name][ext]"
}
},
{
test: /\.ico$/,
type: "asset/resource",
generator: {
filename: "icons/[name][ext]"
}
},
{
test: /\.(woff|woff2?|eot|ttf)$/,
type: "asset/resource",
generator: {
filename: "fonts/[name][ext]"
}
}
]
},
stats: "errors-warnings",
performance: {
// We should eventually consider chunking to decrease
// these values
maxEntrypointSize: 27000000,
maxAssetSize: 27000000
} }
},
plugins: [
new webpack.DefinePlugin({
__OPENMCT_VERSION__: `'${packageDefinition.version}'`,
__OPENMCT_BUILD_DATE__: `'${new Date()}'`,
__OPENMCT_REVISION__: `'${gitRevision}'`,
__OPENMCT_BUILD_BRANCH__: `'${gitBranch}'`
}),
new VueLoaderPlugin(),
new CopyWebpackPlugin({
patterns: [
{
from: 'src/images/favicons',
to: 'favicons'
},
{
from: './index.html',
transform: function (content) {
return content.toString().replace(/dist\//g, '');
}
},
{
from: 'src/plugins/imagery/layers',
to: 'imagery'
}
]
}),
new MiniCssExtractPlugin({
filename: '[name].css',
chunkFilename: '[name].css'
})
],
module: {
rules: [
{
test: /\.(sc|sa|c)ss$/,
use: [
MiniCssExtractPlugin.loader,
{
loader: 'css-loader'
},
{
loader: 'resolve-url-loader'
},
{
loader: 'sass-loader',
options: { sourceMap: true }
}
]
},
{
test: /\.vue$/,
use: 'vue-loader'
},
{
test: /\.html$/,
type: 'asset/source'
},
{
test: /\.(jpg|jpeg|png|svg)$/,
type: 'asset/resource',
generator: {
filename: 'images/[name][ext]'
}
},
{
test: /\.ico$/,
type: 'asset/resource',
generator: {
filename: 'icons/[name][ext]'
}
},
{
test: /\.(woff|woff2?|eot|ttf)$/,
type: 'asset/resource',
generator: {
filename: 'fonts/[name][ext]'
}
}
]
},
stats: 'errors-warnings',
performance: {
// We should eventually consider chunking to decrease
// these values
maxEntrypointSize: 27000000,
maxAssetSize: 27000000
}
}; };
module.exports = config; module.exports = config;

View File

@ -6,32 +6,32 @@ OpenMCT Continuous Integration servers use this configuration to add code covera
information to pull requests. information to pull requests.
*/ */
const config = require("./webpack.dev"); const config = require('./webpack.dev');
// eslint-disable-next-line no-undef // eslint-disable-next-line no-undef
const CI = process.env.CI === "true"; const CI = process.env.CI === 'true';
config.devtool = CI ? false : undefined; config.devtool = CI ? false : undefined;
config.devServer.hot = false; config.devServer.hot = false;
config.module.rules.push({ config.module.rules.push({
test: /\.js$/, test: /\.js$/,
exclude: /(Spec\.js$)|(node_modules)/, exclude: /(Spec\.js$)|(node_modules)/,
use: { use: {
loader: "babel-loader", loader: 'babel-loader',
options: { options: {
retainLines: true, retainLines: true,
// eslint-disable-next-line no-undef // eslint-disable-next-line no-undef
plugins: [ plugins: [
[ [
"babel-plugin-istanbul", 'babel-plugin-istanbul',
{ {
extension: [".js", ".vue"] extension: ['.js', '.vue']
} }
] ]
] ]
}
} }
}
}); });
module.exports = config; module.exports = config;

View File

@ -5,59 +5,59 @@ This configuration should be used for development purposes. It contains full sou
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"); const path = require('path');
const webpack = require("webpack"); const webpack = require('webpack');
const { merge } = require("webpack-merge"); const { merge } = require('webpack-merge');
const common = require("./webpack.common"); const common = require('./webpack.common');
const projectRootDir = path.resolve(__dirname, ".."); const projectRootDir = path.resolve(__dirname, '..');
module.exports = merge(common, { module.exports = 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.
// We need to exclude any files we don't want webpack to watch. // We need to exclude any files we don't want webpack to watch.
// See: https://webpack.js.org/configuration/watch/#watchoptions-exclude // See: https://webpack.js.org/configuration/watch/#watchoptions-exclude
ignored: [ ignored: [
"**/{node_modules,dist,docs,e2e}", // All files in node_modules, dist, docs, e2e, '**/{node_modules,dist,docs,e2e}', // All files in node_modules, dist, docs, e2e,
"**/{*.yml,Procfile,webpack*.js,babel*.js,package*.json,tsconfig.json}", // Config files '**/{*.yml,Procfile,webpack*.js,babel*.js,package*.json,tsconfig.json}', // Config files
"**/*.{sh,md,png,ttf,woff,svg}", // Non source files '**/*.{sh,md,png,ttf,woff,svg}', // Non source files
"**/.*" // dotfiles and dotfolders '**/.*' // dotfiles and dotfolders
] ]
}, },
resolve: { resolve: {
alias: { alias: {
vue: path.join(projectRootDir, "node_modules/vue/dist/vue.js") vue: path.join(projectRootDir, 'node_modules/vue/dist/vue.js')
}
},
plugins: [
new webpack.DefinePlugin({
__OPENMCT_ROOT_RELATIVE__: '"dist/"'
})
],
devtool: "eval-source-map",
devServer: {
devMiddleware: {
writeToDisk: (filePathString) => {
const filePath = path.parse(filePathString);
const shouldWrite = !filePath.base.includes("hot-update");
return shouldWrite;
}
},
watchFiles: ["**/*.css"],
static: {
directory: path.join(__dirname, "..", "/dist"),
publicPath: "/dist",
watch: false
},
client: {
progress: true,
overlay: {
// Disable overlay for runtime errors.
// See: https://github.com/webpack/webpack-dev-server/issues/4771
runtimeErrors: false
}
}
} }
},
plugins: [
new webpack.DefinePlugin({
__OPENMCT_ROOT_RELATIVE__: '"dist/"'
})
],
devtool: 'eval-source-map',
devServer: {
devMiddleware: {
writeToDisk: (filePathString) => {
const filePath = path.parse(filePathString);
const shouldWrite = !filePath.base.includes('hot-update');
return shouldWrite;
}
},
watchFiles: ['**/*.css'],
static: {
directory: path.join(__dirname, '..', '/dist'),
publicPath: '/dist',
watch: false
},
client: {
progress: true,
overlay: {
// Disable overlay for runtime errors.
// See: https://github.com/webpack/webpack-dev-server/issues/4771
runtimeErrors: false
}
}
}
}); });

View File

@ -4,24 +4,24 @@
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 path = require('path');
const webpack = require("webpack"); const webpack = require('webpack');
const { merge } = require("webpack-merge"); const { merge } = require('webpack-merge');
const common = require("./webpack.common"); const common = require('./webpack.common');
const projectRootDir = path.resolve(__dirname, ".."); const projectRootDir = path.resolve(__dirname, '..');
module.exports = merge(common, { module.exports = merge(common, {
mode: "production", mode: 'production',
resolve: { resolve: {
alias: { alias: {
vue: path.join(projectRootDir, "node_modules/vue/dist/vue.min.js") vue: path.join(projectRootDir, 'node_modules/vue/dist/vue.min.js')
} }
}, },
plugins: [ plugins: [
new webpack.DefinePlugin({ new webpack.DefinePlugin({
__OPENMCT_ROOT_RELATIVE__: '""' __OPENMCT_ROOT_RELATIVE__: '""'
}) })
], ],
devtool: "source-map" devtool: 'source-map'
}); });

View File

@ -11,7 +11,7 @@ coverage:
informational: true informational: true
precision: 2 precision: 2
round: down round: down
range: "66...100" range: '66...100'
flags: flags:
unit: unit:
@ -22,7 +22,7 @@ flags:
carryforward: true carryforward: true
comment: comment:
layout: "diff,flags,files,footer" layout: 'diff,flags,files,footer'
behavior: default behavior: default
require_changes: false require_changes: false
show_carryforward_flags: true show_carryforward_flags: true

View File

@ -1,15 +1,15 @@
/* eslint-disable no-undef */ /* eslint-disable no-undef */
module.exports = { module.exports = {
"extends": ["plugin:playwright/playwright-test"], extends: ['plugin:playwright/playwright-test'],
"rules": { rules: {
"playwright/max-nested-describe": ["error", { "max": 1 }] 'playwright/max-nested-describe': ['error', { max: 1 }]
}, },
"overrides": [ overrides: [
{ {
"files": ["tests/visual/*.spec.js"], files: ['tests/visual/*.spec.js'],
"rules": { rules: {
"playwright/no-wait-for-timeout": "off" 'playwright/no-wait-for-timeout': 'off'
} }
} }
] ]
}; };

View File

@ -66,58 +66,58 @@ const { expect } = require('@playwright/test');
* @returns {Promise<CreatedObjectInfo>} An object containing information about the newly created domain object. * @returns {Promise<CreatedObjectInfo>} An object containing information about the newly created domain object.
*/ */
async function createDomainObjectWithDefaults(page, { type, name, parent = 'mine' }) { async function createDomainObjectWithDefaults(page, { type, name, parent = 'mine' }) {
if (!name) { if (!name) {
name = `${type}:${genUuid()}`; name = `${type}:${genUuid()}`;
} }
const parentUrl = await getHashUrlToDomainObject(page, parent); const parentUrl = await getHashUrlToDomainObject(page, parent);
// Navigate to the parent object. This is necessary to create the object // Navigate to the parent object. This is necessary to create the object
// in the correct location, such as a folder, layout, or plot. // in the correct location, such as a folder, layout, or plot.
await page.goto(`${parentUrl}?hideTree=true`); await page.goto(`${parentUrl}?hideTree=true`);
//Click the Create button //Click the Create button
await page.click('button:has-text("Create")'); await page.click('button:has-text("Create")');
// Click the object specified by 'type' // Click the object specified by 'type'
await page.click(`li[role='menuitem']:text("${type}")`); await page.click(`li[role='menuitem']:text("${type}")`);
// Modify the name input field of the domain object to accept 'name' // Modify the name input field of the domain object to accept 'name'
const nameInput = page.locator('form[name="mctForm"] .first input[type="text"]'); const nameInput = page.locator('form[name="mctForm"] .first input[type="text"]');
await nameInput.fill(""); await nameInput.fill('');
await nameInput.fill(name); await nameInput.fill(name);
if (page.testNotes) { if (page.testNotes) {
// Fill the "Notes" section with information about the // Fill the "Notes" section with information about the
// currently running test and its project. // currently running test and its project.
const notesInput = page.locator('form[name="mctForm"] #notes-textarea'); const notesInput = page.locator('form[name="mctForm"] #notes-textarea');
await notesInput.fill(page.testNotes); await notesInput.fill(page.testNotes);
} }
// Click OK button and wait for Navigate event // Click OK button and wait for Navigate event
await Promise.all([ await Promise.all([
page.waitForLoadState(), page.waitForLoadState(),
page.click('[aria-label="Save"]'), page.click('[aria-label="Save"]'),
// Wait for Save Banner to appear // Wait for Save Banner to appear
page.waitForSelector('.c-message-banner__message') page.waitForSelector('.c-message-banner__message')
]); ]);
// Wait until the URL is updated // Wait until the URL is updated
await page.waitForURL(`**/${parent}/*`); await page.waitForURL(`**/${parent}/*`);
const uuid = await getFocusedObjectUuid(page); const uuid = await getFocusedObjectUuid(page);
const objectUrl = await getHashUrlToDomainObject(page, uuid); const objectUrl = await getHashUrlToDomainObject(page, uuid);
if (await _isInEditMode(page, uuid)) { if (await _isInEditMode(page, uuid)) {
// Save (exit edit mode) // Save (exit edit mode)
await page.locator('button[title="Save"]').click(); await page.locator('button[title="Save"]').click();
await page.locator('li[title="Save and Finish Editing"]').click(); await page.locator('li[title="Save and Finish Editing"]').click();
} }
return { return {
name, name,
uuid, uuid,
url: objectUrl url: objectUrl
}; };
} }
/** /**
@ -126,17 +126,17 @@ async function createDomainObjectWithDefaults(page, { type, name, parent = 'mine
* @param {CreateNotificationOptions} createNotificationOptions * @param {CreateNotificationOptions} createNotificationOptions
*/ */
async function createNotification(page, createNotificationOptions) { async function createNotification(page, createNotificationOptions) {
await page.evaluate((_createNotificationOptions) => { await page.evaluate((_createNotificationOptions) => {
const { message, severity, options } = _createNotificationOptions; const { message, severity, options } = _createNotificationOptions;
const notificationApi = window.openmct.notifications; const notificationApi = window.openmct.notifications;
if (severity === 'info') { if (severity === 'info') {
notificationApi.info(message, options); notificationApi.info(message, options);
} else if (severity === 'alert') { } else if (severity === 'alert') {
notificationApi.alert(message, options); notificationApi.alert(message, options);
} else { } else {
notificationApi.error(message, options); notificationApi.error(message, options);
} }
}, createNotificationOptions); }, createNotificationOptions);
} }
/** /**
@ -145,12 +145,12 @@ async function createNotification(page, createNotificationOptions) {
* @param {string} name * @param {string} name
*/ */
async function expandTreePaneItemByName(page, name) { async function expandTreePaneItemByName(page, name) {
const treePane = page.getByRole('tree', { const treePane = page.getByRole('tree', {
name: 'Main Tree' name: 'Main Tree'
}); });
const treeItem = treePane.locator(`role=treeitem[expanded=false][name=/${name}/]`); const treeItem = treePane.locator(`role=treeitem[expanded=false][name=/${name}/]`);
const expandTriangle = treeItem.locator('.c-disclosure-triangle'); const expandTriangle = treeItem.locator('.c-disclosure-triangle');
await expandTriangle.click(); await expandTriangle.click();
} }
/** /**
@ -160,67 +160,67 @@ async function expandTreePaneItemByName(page, name) {
* @returns {Promise<CreatedObjectInfo>} An object containing information about the newly created domain object. * @returns {Promise<CreatedObjectInfo>} An object containing information about the newly created domain object.
*/ */
async function createPlanFromJSON(page, { name, json, parent = 'mine' }) { async function createPlanFromJSON(page, { name, json, parent = 'mine' }) {
if (!name) { if (!name) {
name = `Plan:${genUuid()}`; name = `Plan:${genUuid()}`;
} }
const parentUrl = await getHashUrlToDomainObject(page, parent); const parentUrl = await getHashUrlToDomainObject(page, parent);
// Navigate to the parent object. This is necessary to create the object // Navigate to the parent object. This is necessary to create the object
// in the correct location, such as a folder, layout, or plot. // in the correct location, such as a folder, layout, or plot.
await page.goto(`${parentUrl}?hideTree=true`); await page.goto(`${parentUrl}?hideTree=true`);
// Click the Create button // Click the Create button
await page.click('button:has-text("Create")'); await page.click('button:has-text("Create")');
// Click 'Plan' menu option // Click 'Plan' menu option
await page.click(`li:text("Plan")`); await page.click(`li:text("Plan")`);
// Modify the name input field of the domain object to accept 'name' // Modify the name input field of the domain object to accept 'name'
const nameInput = page.locator('form[name="mctForm"] .first input[type="text"]'); const nameInput = page.locator('form[name="mctForm"] .first input[type="text"]');
await nameInput.fill(""); await nameInput.fill('');
await nameInput.fill(name); await nameInput.fill(name);
// Upload buffer from memory // Upload buffer from memory
await page.locator('input#fileElem').setInputFiles({ await page.locator('input#fileElem').setInputFiles({
name: 'plan.txt', name: 'plan.txt',
mimeType: 'text/plain', mimeType: 'text/plain',
buffer: Buffer.from(JSON.stringify(json)) buffer: Buffer.from(JSON.stringify(json))
}); });
// Click OK button and wait for Navigate event // Click OK button and wait for Navigate event
await Promise.all([ await Promise.all([
page.waitForLoadState(), page.waitForLoadState(),
page.click('[aria-label="Save"]'), page.click('[aria-label="Save"]'),
// Wait for Save Banner to appear // Wait for Save Banner to appear
page.waitForSelector('.c-message-banner__message') page.waitForSelector('.c-message-banner__message')
]); ]);
// Wait until the URL is updated // Wait until the URL is updated
await page.waitForURL(`**/${parent}/*`); await page.waitForURL(`**/${parent}/*`);
const uuid = await getFocusedObjectUuid(page); const uuid = await getFocusedObjectUuid(page);
const objectUrl = await getHashUrlToDomainObject(page, uuid); const objectUrl = await getHashUrlToDomainObject(page, uuid);
return { return {
uuid, uuid,
name, name,
url: objectUrl url: objectUrl
}; };
} }
/** /**
* Open the given `domainObject`'s context menu from the object tree. * Open the given `domainObject`'s context menu from the object tree.
* Expands the path to the object and scrolls to it if necessary. * Expands the path to the object and scrolls to it if necessary.
* *
* @param {import('@playwright/test').Page} page * @param {import('@playwright/test').Page} page
* @param {string} url the url to the object * @param {string} url the url to the object
*/ */
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.click('button[title="Show selected item in tree"]');
await page.locator('.is-navigated-object').click({ await page.locator('.is-navigated-object').click({
button: 'right' button: 'right'
}); });
} }
/** /**
@ -228,23 +228,25 @@ async function openObjectTreeContextMenu(page, url) {
* @param {import('@playwright/test').Page} page * @param {import('@playwright/test').Page} page
* @param {"Main Tree" | "Create Modal Tree"} [treeName="Main Tree"] * @param {"Main Tree" | "Create Modal Tree"} [treeName="Main Tree"]
*/ */
async function expandEntireTree(page, treeName = "Main Tree") { async function expandEntireTree(page, treeName = 'Main Tree') {
const treeLocator = page.getByRole('tree', { const treeLocator = page.getByRole('tree', {
name: treeName name: treeName
}); });
const collapsedTreeItems = treeLocator.getByRole('treeitem', { const collapsedTreeItems = treeLocator
expanded: false .getByRole('treeitem', {
}).locator('span.c-disclosure-triangle.is-enabled'); expanded: false
})
.locator('span.c-disclosure-triangle.is-enabled');
while (await collapsedTreeItems.count() > 0) { while ((await collapsedTreeItems.count()) > 0) {
await collapsedTreeItems.nth(0).click(); await collapsedTreeItems.nth(0).click();
// FIXME: Replace hard wait with something event-driven. // FIXME: Replace hard wait with something event-driven.
// Without the wait, this fails periodically due to a race condition // Without the wait, this fails periodically due to a race condition
// with Vue rendering (loop exits prematurely). // with Vue rendering (loop exits prematurely).
// eslint-disable-next-line playwright/no-wait-for-timeout // eslint-disable-next-line playwright/no-wait-for-timeout
await page.waitForTimeout(200); await page.waitForTimeout(200);
} }
} }
/** /**
@ -254,12 +256,12 @@ async function expandEntireTree(page, treeName = "Main Tree") {
* @returns {Promise<string>} the uuid of the focused object * @returns {Promise<string>} the uuid of the focused object
*/ */
async function getFocusedObjectUuid(page) { async function getFocusedObjectUuid(page) {
const UUIDv4Regexp = /[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}/gi; const UUIDv4Regexp = /[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}/gi;
const focusedObjectUuid = await page.evaluate((regexp) => { const focusedObjectUuid = await page.evaluate((regexp) => {
return window.location.href.split('?')[0].match(regexp).at(-1); return window.location.href.split('?')[0].match(regexp).at(-1);
}, UUIDv4Regexp); }, UUIDv4Regexp);
return focusedObjectUuid; return focusedObjectUuid;
} }
/** /**
@ -273,22 +275,25 @@ async function getFocusedObjectUuid(page) {
* @returns {Promise<string>} the url of the object * @returns {Promise<string>} the url of the object
*/ */
async function getHashUrlToDomainObject(page, uuid) { async function getHashUrlToDomainObject(page, uuid) {
await page.waitForLoadState('load'); //Add some determinism await page.waitForLoadState('load'); //Add some determinism
const hashUrl = await page.evaluate(async (objectUuid) => { const hashUrl = await page.evaluate(async (objectUuid) => {
const path = await window.openmct.objects.getOriginalPath(objectUuid); const path = await window.openmct.objects.getOriginalPath(objectUuid);
let url = './#/browse/' + [...path].reverse() let url =
.map((object) => window.openmct.objects.makeKeyString(object.identifier)) './#/browse/' +
.join('/'); [...path]
.reverse()
.map((object) => window.openmct.objects.makeKeyString(object.identifier))
.join('/');
// Drop the vestigial '/ROOT' if it exists // Drop the vestigial '/ROOT' if it exists
if (url.includes('/ROOT')) { if (url.includes('/ROOT')) {
url = url.split('/ROOT').join(''); url = url.split('/ROOT').join('');
} }
return url; return url;
}, uuid); }, uuid);
return hashUrl; return hashUrl;
} }
/** /**
@ -298,8 +303,8 @@ async function getHashUrlToDomainObject(page, uuid) {
* @return {Promise<boolean>} true if the Open MCT is in Edit Mode * @return {Promise<boolean>} true if the Open MCT is in Edit Mode
*/ */
async function _isInEditMode(page, identifier) { async function _isInEditMode(page, identifier) {
// eslint-disable-next-line no-return-await // eslint-disable-next-line no-return-await
return await page.evaluate(() => window.openmct.editor.isEditing()); return await page.evaluate(() => window.openmct.editor.isEditing());
} }
/** /**
@ -308,15 +313,15 @@ async function _isInEditMode(page, identifier) {
* @param {boolean} [isFixedTimespan=true] true for fixed timespan mode, false for realtime mode; default is true * @param {boolean} [isFixedTimespan=true] true for fixed timespan mode, false for realtime mode; default is true
*/ */
async function setTimeConductorMode(page, isFixedTimespan = true) { async function setTimeConductorMode(page, isFixedTimespan = true) {
// Click 'mode' button // Click 'mode' button
await page.locator('.c-mode-button').click(); await page.locator('.c-mode-button').click();
// Switch time conductor mode // Switch time conductor mode
if (isFixedTimespan) { if (isFixedTimespan) {
await page.locator('data-testid=conductor-modeOption-fixed').click(); await page.locator('data-testid=conductor-modeOption-fixed').click();
} else { } else {
await page.locator('data-testid=conductor-modeOption-realtime').click(); await page.locator('data-testid=conductor-modeOption-realtime').click();
} }
} }
/** /**
@ -324,7 +329,7 @@ async function setTimeConductorMode(page, isFixedTimespan = true) {
* @param {import('@playwright/test').Page} page * @param {import('@playwright/test').Page} page
*/ */
async function setFixedTimeMode(page) { async function setFixedTimeMode(page) {
await setTimeConductorMode(page, true); await setTimeConductorMode(page, true);
} }
/** /**
@ -332,7 +337,7 @@ async function setFixedTimeMode(page) {
* @param {import('@playwright/test').Page} page * @param {import('@playwright/test').Page} page
*/ */
async function setRealTimeMode(page) { async function setRealTimeMode(page) {
await setTimeConductorMode(page, false); await setTimeConductorMode(page, false);
} }
/** /**
@ -348,23 +353,23 @@ async function setRealTimeMode(page) {
* @param {OffsetValues} offset * @param {OffsetValues} offset
* @param {import('@playwright/test').Locator} offsetButton * @param {import('@playwright/test').Locator} offsetButton
*/ */
async function setTimeConductorOffset(page, {hours, mins, secs}, offsetButton) { async function setTimeConductorOffset(page, { hours, mins, secs }, offsetButton) {
await offsetButton.click(); await offsetButton.click();
if (hours) { if (hours) {
await page.fill('.pr-time-controls__hrs', hours); await page.fill('.pr-time-controls__hrs', hours);
} }
if (mins) { if (mins) {
await page.fill('.pr-time-controls__mins', mins); await page.fill('.pr-time-controls__mins', mins);
} }
if (secs) { if (secs) {
await page.fill('.pr-time-controls__secs', secs); await page.fill('.pr-time-controls__secs', secs);
} }
// Click the check button // Click the check button
await page.locator('.pr-time__buttons .icon-check').click(); await page.locator('.pr-time__buttons .icon-check').click();
} }
/** /**
@ -373,8 +378,8 @@ async function setTimeConductorOffset(page, {hours, mins, secs}, offsetButton) {
* @param {OffsetValues} offset * @param {OffsetValues} offset
*/ */
async function setStartOffset(page, offset) { async function setStartOffset(page, offset) {
const startOffsetButton = page.locator('data-testid=conductor-start-offset-button'); const startOffsetButton = page.locator('data-testid=conductor-start-offset-button');
await setTimeConductorOffset(page, offset, startOffsetButton); await setTimeConductorOffset(page, offset, startOffsetButton);
} }
/** /**
@ -383,8 +388,8 @@ async function setStartOffset(page, offset) {
* @param {OffsetValues} offset * @param {OffsetValues} offset
*/ */
async function setEndOffset(page, offset) { async function setEndOffset(page, offset) {
const endOffsetButton = page.locator('data-testid=conductor-end-offset-button'); const endOffsetButton = page.locator('data-testid=conductor-end-offset-button');
await setTimeConductorOffset(page, offset, endOffsetButton); await setTimeConductorOffset(page, offset, endOffsetButton);
} }
/** /**
@ -394,34 +399,34 @@ async function setEndOffset(page, offset) {
* @param {String} name the name of the tab * @param {String} name the name of the tab
*/ */
async function selectInspectorTab(page, name) { async function selectInspectorTab(page, name) {
const inspectorTabs = page.getByRole('tablist'); const inspectorTabs = page.getByRole('tablist');
const inspectorTab = inspectorTabs.getByTitle(name); const inspectorTab = inspectorTabs.getByTitle(name);
const inspectorTabClass = await inspectorTab.getAttribute('class'); const inspectorTabClass = await inspectorTab.getAttribute('class');
const isSelectedInspectorTab = inspectorTabClass.includes('is-current'); const isSelectedInspectorTab = inspectorTabClass.includes('is-current');
// do not click a tab that is already selected or it will timeout your test // do not click a tab that is already selected or it will timeout your test
// do to a { pointer-events: none; } on selected tabs // do to a { pointer-events: none; } on selected tabs
if (!isSelectedInspectorTab) { if (!isSelectedInspectorTab) {
await inspectorTab.click(); await inspectorTab.click();
} }
} }
/** /**
* Waits and asserts that all plot series data on the page * Waits and asserts that all plot series data on the page
* is loaded and drawn. * is loaded and drawn.
* *
* In lieu of a better way to detect when a plot is done rendering, * In lieu of a better way to detect when a plot is done rendering,
* we [attach a class to the '.gl-plot' element](https://github.com/nasa/openmct/blob/5924d7ea95a0c2d4141c602a3c7d0665cb91095f/src/plugins/plot/MctPlot.vue#L27) * we [attach a class to the '.gl-plot' element](https://github.com/nasa/openmct/blob/5924d7ea95a0c2d4141c602a3c7d0665cb91095f/src/plugins/plot/MctPlot.vue#L27)
* once all pending series data has been loaded. The following appAction retrieves * once all pending series data has been loaded. The following appAction retrieves
* all plots on the page and waits up to the default timeout for the class to be * all plots on the page and waits up to the default timeout for the class to be
* attached to each plot. * attached to each plot.
* @param {import('@playwright/test').Page} page * @param {import('@playwright/test').Page} page
*/ */
async function waitForPlotsToRender(page) { async function waitForPlotsToRender(page) {
const plotLocator = page.locator('.gl-plot'); const plotLocator = page.locator('.gl-plot');
for (const plot of await plotLocator.all()) { for (const plot of await plotLocator.all()) {
await expect(plot).toHaveClass(/js-series-data-loaded/); await expect(plot).toHaveClass(/js-series-data-loaded/);
} }
} }
/** /**
@ -441,57 +446,70 @@ async function waitForPlotsToRender(page) {
* @return {Promise<PlotPixel[]>} * @return {Promise<PlotPixel[]>}
*/ */
async function getCanvasPixels(page, canvasSelector) { async function getCanvasPixels(page, canvasSelector) {
const getTelemValuePromise = new Promise(resolve => page.exposeFunction('getCanvasValue', resolve)); const getTelemValuePromise = new Promise((resolve) =>
const canvasHandle = await page.evaluateHandle((canvas) => document.querySelector(canvas), canvasSelector); page.exposeFunction('getCanvasValue', resolve)
const canvasContextHandle = await page.evaluateHandle(canvas => canvas.getContext('2d'), canvasHandle); );
const canvasHandle = await page.evaluateHandle(
(canvas) => document.querySelector(canvas),
canvasSelector
);
const canvasContextHandle = await page.evaluateHandle(
(canvas) => canvas.getContext('2d'),
canvasHandle
);
await waitForPlotsToRender(page); await waitForPlotsToRender(page);
await page.evaluate(([canvas, ctx]) => { await page.evaluate(
// The document canvas is where the plot points and lines are drawn. ([canvas, ctx]) => {
// The only way to access the canvas is using document (using page.evaluate) // The document canvas is where the plot points and lines are drawn.
/** @type {ImageData} */ // The only way to access the canvas is using document (using page.evaluate)
const data = ctx.getImageData(0, 0, canvas.width, canvas.height).data; /** @type {ImageData} */
/** @type {number[]} */ const data = ctx.getImageData(0, 0, canvas.width, canvas.height).data;
const imageDataValues = Object.values(data); /** @type {number[]} */
/** @type {PlotPixel[]} */ const imageDataValues = Object.values(data);
const plotPixels = []; /** @type {PlotPixel[]} */
// Each pixel consists of four values within the ImageData.data array. The for loop iterates by multiples of four. const plotPixels = [];
// The values associated with each pixel are R (red), G (green), B (blue), and A (alpha), in that order. // Each pixel consists of four values within the ImageData.data array. The for loop iterates by multiples of four.
for (let i = 0; i < imageDataValues.length;) { // The values associated with each pixel are R (red), G (green), B (blue), and A (alpha), in that order.
if (imageDataValues[i] > 0) { for (let i = 0; i < imageDataValues.length; ) {
plotPixels.push({ if (imageDataValues[i] > 0) {
r: imageDataValues[i], plotPixels.push({
g: imageDataValues[i + 1], r: imageDataValues[i],
b: imageDataValues[i + 2], g: imageDataValues[i + 1],
a: imageDataValues[i + 3], b: imageDataValues[i + 2],
strValue: `rgb(${imageDataValues[i]}, ${imageDataValues[i + 1]}, ${imageDataValues[i + 2]}, ${imageDataValues[i + 3]})` a: imageDataValues[i + 3],
}); strValue: `rgb(${imageDataValues[i]}, ${imageDataValues[i + 1]}, ${
} imageDataValues[i + 2]
}, ${imageDataValues[i + 3]})`
i = i + 4; });
} }
window.getCanvasValue(plotPixels); i = i + 4;
}, [canvasHandle, canvasContextHandle]); }
return getTelemValuePromise; window.getCanvasValue(plotPixels);
},
[canvasHandle, canvasContextHandle]
);
return getTelemValuePromise;
} }
// eslint-disable-next-line no-undef // eslint-disable-next-line no-undef
module.exports = { module.exports = {
createDomainObjectWithDefaults, createDomainObjectWithDefaults,
createNotification, createNotification,
createPlanFromJSON, createPlanFromJSON,
expandEntireTree, expandEntireTree,
expandTreePaneItemByName, expandTreePaneItemByName,
getCanvasPixels, getCanvasPixels,
getHashUrlToDomainObject, getHashUrlToDomainObject,
getFocusedObjectUuid, getFocusedObjectUuid,
openObjectTreeContextMenu, openObjectTreeContextMenu,
setFixedTimeMode, setFixedTimeMode,
setRealTimeMode, setRealTimeMode,
setStartOffset, setStartOffset,
setEndOffset, setEndOffset,
selectInspectorTab, selectInspectorTab,
waitForPlotsToRender waitForPlotsToRender
}; };

View File

@ -43,9 +43,9 @@ const sinon = require('sinon');
* @returns {String} formatted string with message type, text, url, and line and column numbers * @returns {String} formatted string with message type, text, url, and line and column numbers
*/ */
function _consoleMessageToString(msg) { function _consoleMessageToString(msg) {
const { url, lineNumber, columnNumber } = msg.location(); const { url, lineNumber, columnNumber } = msg.location();
return `[${msg.type()}] ${msg.text()} at (${url} ${lineNumber}:${columnNumber})`; return `[${msg.type()}] ${msg.text()} at (${url} ${lineNumber}:${columnNumber})`;
} }
/** /**
@ -56,12 +56,9 @@ function _consoleMessageToString(msg) {
* @return {Promise<Animation[]>} * @return {Promise<Animation[]>}
*/ */
function waitForAnimations(locator) { function waitForAnimations(locator) {
return locator return locator.evaluate((element) =>
.evaluate((element) => Promise.all(element.getAnimations({ subtree: true }).map((animation) => animation.finished))
Promise.all( );
element
.getAnimations({ subtree: true })
.map((animation) => animation.finished)));
} }
/** /**
@ -72,103 +69,113 @@ function waitForAnimations(locator) {
const istanbulCLIOutput = path.join(process.cwd(), '.nyc_output'); const istanbulCLIOutput = path.join(process.cwd(), '.nyc_output');
exports.test = base.test.extend({ exports.test = base.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.
* Usage: * Usage:
* ``` * ```
* test.use({ * test.use({
* clockOptions: { * clockOptions: {
* now: 0, * now: 0,
* shouldAdvanceTime: true * shouldAdvanceTime: true
* ``` * ```
* If clockOptions are provided, will override the default clock with fake timers provided by SinonJS. * If clockOptions are provided, will override the default clock with fake timers provided by SinonJS.
* *
* Default: `undefined` * Default: `undefined`
* *
* @see {@link https://github.com/microsoft/playwright/issues/6347 Github RFE} * @see {@link https://github.com/microsoft/playwright/issues/6347 Github RFE}
* @see {@link https://github.com/sinonjs/fake-timers/#var-clock--faketimersinstallconfig SinonJS FakeTimers Config} * @see {@link https://github.com/sinonjs/fake-timers/#var-clock--faketimersinstallconfig SinonJS FakeTimers Config}
*/ */
clockOptions: [undefined, { option: true }], clockOptions: [undefined, { option: true }],
overrideClock: [async ({ context, clockOptions }, use) => { overrideClock: [
if (clockOptions !== undefined) { async ({ context, clockOptions }, use) => {
await context.addInitScript({ if (clockOptions !== undefined) {
path: path.join(__dirname, '../', './node_modules/sinon/pkg/sinon.js') await context.addInitScript({
}); path: path.join(__dirname, '../', './node_modules/sinon/pkg/sinon.js')
await context.addInitScript((options) => {
window.__clock = sinon.useFakeTimers(options);
}, clockOptions);
}
await use(context);
}, {
auto: true,
scope: 'test'
}],
/**
* Extends the base context class to add codecoverage shim.
* @see {@link https://github.com/mxschmitt/playwright-test-coverage Github Project}
*/
context: async ({ context }, use) => {
await context.addInitScript(() =>
window.addEventListener('beforeunload', () =>
(window).collectIstanbulCoverage(JSON.stringify((window).__coverage__))
)
);
await fs.promises.mkdir(istanbulCLIOutput, { recursive: true });
await context.exposeFunction('collectIstanbulCoverage', (coverageJSON) => {
if (coverageJSON) {
fs.writeFileSync(path.join(istanbulCLIOutput, `playwright_coverage_${uuid()}.json`), coverageJSON);
}
}); });
await context.addInitScript((options) => {
window.__clock = sinon.useFakeTimers(options);
}, clockOptions);
}
await use(context); await use(context);
for (const page of context.pages()) {
await page.evaluate(() => (window).collectIstanbulCoverage(JSON.stringify((window).__coverage__)));
}
}, },
/** {
* If true, will assert against any console.error calls that occur during the test. Assertions occur auto: true,
* during test teardown (after the test has completed). scope: 'test'
*
* Default: `true`
*/
failOnConsoleError: [true, { option: true }],
/**
* Extends the base page class to enable console log error detection.
* @see {@link https://github.com/microsoft/playwright/discussions/11690 Github Discussion}
*/
page: async ({ page, failOnConsoleError }, use) => {
// Capture any console errors during test execution
const messages = [];
page.on('console', (msg) => messages.push(msg));
await use(page);
// Assert against console errors during teardown
if (failOnConsoleError) {
messages.forEach(
msg => expect.soft(msg.type(), `Console error detected: ${_consoleMessageToString(msg)}`).not.toEqual('error')
);
}
},
/**
* Extends the base browser class to enable CDP connection definition in playwright.config.js. Once
* that RFE is implemented, this function can be removed.
* @see {@link https://github.com/microsoft/playwright/issues/8379 Github RFE}
*/
browser: async ({ playwright, browser }, use, workerInfo) => {
// Use browserless if configured
if (workerInfo.project.name.match(/browserless/)) {
const vBrowser = await playwright.chromium.connectOverCDP({
endpointURL: 'ws://localhost:3003'
});
await use(vBrowser);
} else {
// Use Local Browser for testing.
await use(browser);
}
} }
],
/**
* Extends the base context class to add codecoverage shim.
* @see {@link https://github.com/mxschmitt/playwright-test-coverage Github Project}
*/
context: async ({ context }, use) => {
await context.addInitScript(() =>
window.addEventListener('beforeunload', () =>
window.collectIstanbulCoverage(JSON.stringify(window.__coverage__))
)
);
await fs.promises.mkdir(istanbulCLIOutput, { recursive: true });
await context.exposeFunction('collectIstanbulCoverage', (coverageJSON) => {
if (coverageJSON) {
fs.writeFileSync(
path.join(istanbulCLIOutput, `playwright_coverage_${uuid()}.json`),
coverageJSON
);
}
});
await use(context);
for (const page of context.pages()) {
await page.evaluate(() =>
window.collectIstanbulCoverage(JSON.stringify(window.__coverage__))
);
}
},
/**
* If true, will assert against any console.error calls that occur during the test. Assertions occur
* during test teardown (after the test has completed).
*
* Default: `true`
*/
failOnConsoleError: [true, { option: true }],
/**
* Extends the base page class to enable console log error detection.
* @see {@link https://github.com/microsoft/playwright/discussions/11690 Github Discussion}
*/
page: async ({ page, failOnConsoleError }, use) => {
// Capture any console errors during test execution
const messages = [];
page.on('console', (msg) => messages.push(msg));
await use(page);
// Assert against console errors during teardown
if (failOnConsoleError) {
messages.forEach((msg) =>
expect
.soft(msg.type(), `Console error detected: ${_consoleMessageToString(msg)}`)
.not.toEqual('error')
);
}
},
/**
* Extends the base browser class to enable CDP connection definition in playwright.config.js. Once
* that RFE is implemented, this function can be removed.
* @see {@link https://github.com/microsoft/playwright/issues/8379 Github RFE}
*/
browser: async ({ playwright, browser }, use, workerInfo) => {
// Use browserless if configured
if (workerInfo.project.name.match(/browserless/)) {
const vBrowser = await playwright.chromium.connectOverCDP({
endpointURL: 'ws://localhost:3003'
});
await use(vBrowser);
} else {
// Use Local Browser for testing.
await use(browser);
}
}
}); });
exports.expect = expect; exports.expect = expect;

View File

@ -23,6 +23,6 @@
// This should be used to install the Example Fault Provider, this will also install the FaultManagementPlugin (neither of which are installed by default). // This should be used to install the Example Fault Provider, this will also install the FaultManagementPlugin (neither of which are installed by default).
document.addEventListener('DOMContentLoaded', () => { document.addEventListener('DOMContentLoaded', () => {
const openmct = window.openmct; const openmct = window.openmct;
openmct.install(openmct.plugins.example.ExampleFaultSource()); openmct.install(openmct.plugins.example.ExampleFaultSource());
}); });

View File

@ -23,8 +23,8 @@
// This should be used to install the Example Fault Provider, this will also install the FaultManagementPlugin (neither of which are installed by default). // This should be used to install the Example Fault Provider, this will also install the FaultManagementPlugin (neither of which are installed by default).
document.addEventListener('DOMContentLoaded', () => { document.addEventListener('DOMContentLoaded', () => {
const openmct = window.openmct; const openmct = window.openmct;
const staticFaults = true; const staticFaults = true;
openmct.install(openmct.plugins.example.ExampleFaultSource(staticFaults)); openmct.install(openmct.plugins.example.ExampleFaultSource(staticFaults));
}); });

View File

@ -22,6 +22,6 @@
// This should be used to install the Example User // This should be used to install the Example User
document.addEventListener('DOMContentLoaded', () => { document.addEventListener('DOMContentLoaded', () => {
const openmct = window.openmct; const openmct = window.openmct;
openmct.install(openmct.plugins.example.ExampleUser()); openmct.install(openmct.plugins.example.ExampleUser());
}); });

View File

@ -23,6 +23,6 @@
// This should be used to install the Example Fault Provider, this will also install the FaultManagementPlugin (neither of which are installed by default). // This should be used to install the Example Fault Provider, this will also install the FaultManagementPlugin (neither of which are installed by default).
document.addEventListener('DOMContentLoaded', () => { document.addEventListener('DOMContentLoaded', () => {
const openmct = window.openmct; const openmct = window.openmct;
openmct.install(openmct.plugins.FaultManagement()); openmct.install(openmct.plugins.FaultManagement());
}); });

View File

@ -1,76 +1,71 @@
class DomainObjectViewProvider { class DomainObjectViewProvider {
constructor(openmct) { constructor(openmct) {
this.key = 'doViewProvider'; this.key = 'doViewProvider';
this.name = 'Domain Object View Provider'; this.name = 'Domain Object View Provider';
this.openmct = openmct; this.openmct = openmct;
} }
canView(domainObject) { canView(domainObject) {
return domainObject.type === 'imageFileInput' return domainObject.type === 'imageFileInput' || domainObject.type === 'jsonFileInput';
|| domainObject.type === 'jsonFileInput'; }
}
view(domainObject, objectPath) { view(domainObject, objectPath) {
let content; let content;
return { return {
show: function (element) { show: function (element) {
const body = domainObject.selectFile.body; const body = domainObject.selectFile.body;
const type = typeof body; const type = typeof body;
content = document.createElement('div'); content = document.createElement('div');
content.id = 'file-input-type'; content.id = 'file-input-type';
content.textContent = JSON.stringify(type); content.textContent = JSON.stringify(type);
element.appendChild(content); element.appendChild(content);
}, },
destroy: function (element) { destroy: function (element) {
element.removeChild(content); element.removeChild(content);
content = undefined; content = undefined;
} }
}; };
} }
} }
document.addEventListener('DOMContentLoaded', () => { document.addEventListener('DOMContentLoaded', () => {
const openmct = window.openmct; const openmct = window.openmct;
openmct.types.addType('jsonFileInput', { openmct.types.addType('jsonFileInput', {
key: 'jsonFileInput', key: 'jsonFileInput',
name: "JSON File Input Object", name: 'JSON File Input Object',
creatable: true, creatable: true,
form: [ form: [
{ {
name: 'Upload File', name: 'Upload File',
key: 'selectFile', key: 'selectFile',
control: 'file-input', control: 'file-input',
required: true, required: true,
text: 'Select File...', text: 'Select File...',
type: 'application/json', type: 'application/json',
property: [ property: ['selectFile']
"selectFile" }
] ]
} });
]
});
openmct.types.addType('imageFileInput', { openmct.types.addType('imageFileInput', {
key: 'imageFileInput', key: 'imageFileInput',
name: "Image File Input Object", name: 'Image File Input Object',
creatable: true, creatable: true,
form: [ form: [
{ {
name: 'Upload File', name: 'Upload File',
key: 'selectFile', key: 'selectFile',
control: 'file-input', control: 'file-input',
required: true, required: true,
text: 'Select File...', text: 'Select File...',
type: 'image/*', type: 'image/*',
property: [ property: ['selectFile']
"selectFile" }
] ]
} });
]
});
openmct.objectViews.addProvider(new DomainObjectViewProvider(openmct)); openmct.objectViews.addProvider(new DomainObjectViewProvider(openmct));
}); });

View File

@ -27,6 +27,6 @@ const NOTEBOOK_NAME = 'Notebook';
const URL_WHITELIST = ['google.com']; const URL_WHITELIST = ['google.com'];
document.addEventListener('DOMContentLoaded', () => { document.addEventListener('DOMContentLoaded', () => {
const openmct = window.openmct; const openmct = window.openmct;
openmct.install(openmct.plugins.Notebook(NOTEBOOK_NAME, URL_WHITELIST)); openmct.install(openmct.plugins.Notebook(NOTEBOOK_NAME, URL_WHITELIST));
}); });

View File

@ -22,6 +22,6 @@
// This should be used to install the Operator Status // This should be used to install the Operator Status
document.addEventListener('DOMContentLoaded', () => { document.addEventListener('DOMContentLoaded', () => {
const openmct = window.openmct; const openmct = window.openmct;
openmct.install(openmct.plugins.OperatorStatus()); openmct.install(openmct.plugins.OperatorStatus());
}); });

View File

@ -25,6 +25,6 @@
// await page.addInitScript({ path: path.join(__dirname, 'addInitRestrictedNotebook.js') }); // await page.addInitScript({ path: path.join(__dirname, 'addInitRestrictedNotebook.js') });
document.addEventListener('DOMContentLoaded', () => { document.addEventListener('DOMContentLoaded', () => {
const openmct = window.openmct; const openmct = window.openmct;
openmct.install(openmct.plugins.RestrictedNotebook('CUSTOM_NAME')); openmct.install(openmct.plugins.RestrictedNotebook('CUSTOM_NAME'));
}); });

View File

@ -1,27 +1,27 @@
(function () { (function () {
document.addEventListener('DOMContentLoaded', () => { document.addEventListener('DOMContentLoaded', () => {
const PERSISTENCE_KEY = 'persistence-tests'; const PERSISTENCE_KEY = 'persistence-tests';
const openmct = window.openmct; const openmct = window.openmct;
openmct.objects.addRoot({ openmct.objects.addRoot({
namespace: PERSISTENCE_KEY, namespace: PERSISTENCE_KEY,
key: PERSISTENCE_KEY key: PERSISTENCE_KEY
});
openmct.objects.addProvider(PERSISTENCE_KEY, {
get(identifier) {
if (identifier.key !== PERSISTENCE_KEY) {
return undefined;
} else {
return Promise.resolve({
identifier,
type: 'folder',
name: 'Persistence Testing',
location: 'ROOT',
composition: []
});
}
}
});
}); });
}());
openmct.objects.addProvider(PERSISTENCE_KEY, {
get(identifier) {
if (identifier.key !== PERSISTENCE_KEY) {
return undefined;
} else {
return Promise.resolve({
identifier,
type: 'folder',
name: 'Persistence Testing',
location: 'ROOT',
composition: []
});
}
}
});
});
})();

View File

@ -26,254 +26,268 @@ 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: path.join(__dirname, './', 'addInitExampleFaultProvider.js') });
await navigateToFaultItemInTree(page); await navigateToFaultItemInTree(page);
} }
/** /**
* @param {import('@playwright/test').Page} page * @param {import('@playwright/test').Page} page
*/ */
async function navigateToFaultManagementWithStaticExample(page) { async function navigateToFaultManagementWithStaticExample(page) {
await page.addInitScript({ path: path.join(__dirname, './', 'addInitExampleFaultProviderStatic.js') }); await page.addInitScript({
path: path.join(__dirname, './', 'addInitExampleFaultProviderStatic.js')
});
await navigateToFaultItemInTree(page); await navigateToFaultItemInTree(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: path.join(__dirname, './', 'addInitFaultManagementPlugin.js') });
await navigateToFaultItemInTree(page); await navigateToFaultItemInTree(page);
} }
/** /**
* @param {import('@playwright/test').Page} page * @param {import('@playwright/test').Page} page
*/ */
async function navigateToFaultItemInTree(page) { async function navigateToFaultItemInTree(page) {
await page.goto('./', { waitUntil: 'networkidle' }); await page.goto('./', { waitUntil: 'networkidle' });
const faultManagementTreeItem = page.getByRole('tree', { const faultManagementTreeItem = page
name: "Main Tree" .getByRole('tree', {
}).getByRole('treeitem', { name: 'Main Tree'
name: "Fault Management" })
.getByRole('treeitem', {
name: 'Fault Management'
}); });
// Navigate to "Fault Management" from the tree // Navigate to "Fault Management" from the tree
await faultManagementTreeItem.click(); await faultManagementTreeItem.click();
} }
/** /**
* @param {import('@playwright/test').Page} page * @param {import('@playwright/test').Page} page
*/ */
async function acknowledgeFault(page, rowNumber) { async function acknowledgeFault(page, rowNumber) {
await openFaultRowMenu(page, rowNumber); await openFaultRowMenu(page, rowNumber);
await page.locator('.c-menu >> text="Acknowledge"').click(); await page.locator('.c-menu >> text="Acknowledge"').click();
// Click [aria-label="Save"] // Click [aria-label="Save"]
await page.locator('[aria-label="Save"]').click(); await page.locator('[aria-label="Save"]').click();
} }
/** /**
* @param {import('@playwright/test').Page} page * @param {import('@playwright/test').Page} page
*/ */
async function shelveMultipleFaults(page, ...nums) { async function shelveMultipleFaults(page, ...nums) {
const selectRows = nums.map((num) => { const selectRows = nums.map((num) => {
return selectFaultItem(page, num); return selectFaultItem(page, num);
}); });
await Promise.all(selectRows); await Promise.all(selectRows);
await page.locator('button:has-text("Shelve")').click(); await page.locator('button:has-text("Shelve")').click();
await page.locator('[aria-label="Save"]').click(); await page.locator('[aria-label="Save"]').click();
} }
/** /**
* @param {import('@playwright/test').Page} page * @param {import('@playwright/test').Page} page
*/ */
async function acknowledgeMultipleFaults(page, ...nums) { async function acknowledgeMultipleFaults(page, ...nums) {
const selectRows = nums.map((num) => { const selectRows = nums.map((num) => {
return selectFaultItem(page, num); return selectFaultItem(page, num);
}); });
await Promise.all(selectRows); await Promise.all(selectRows);
await page.locator('button:has-text("Acknowledge")').click(); await page.locator('button:has-text("Acknowledge")').click();
await page.locator('[aria-label="Save"]').click(); await page.locator('[aria-label="Save"]').click();
} }
/** /**
* @param {import('@playwright/test').Page} page * @param {import('@playwright/test').Page} page
*/ */
async function shelveFault(page, rowNumber) { async function shelveFault(page, rowNumber) {
await openFaultRowMenu(page, rowNumber); await openFaultRowMenu(page, rowNumber);
await page.locator('.c-menu >> text="Shelve"').click(); await page.locator('.c-menu >> text="Shelve"').click();
// Click [aria-label="Save"] // Click [aria-label="Save"]
await page.locator('[aria-label="Save"]').click(); await page.locator('[aria-label="Save"]').click();
} }
/** /**
* @param {import('@playwright/test').Page} page * @param {import('@playwright/test').Page} page
*/ */
async function changeViewTo(page, view) { async function changeViewTo(page, view) {
await page.locator('.c-fault-mgmt__search-row select').first().selectOption(view); await page.locator('.c-fault-mgmt__search-row select').first().selectOption(view);
} }
/** /**
* @param {import('@playwright/test').Page} page * @param {import('@playwright/test').Page} page
*/ */
async function sortFaultsBy(page, sort) { async function sortFaultsBy(page, sort) {
await page.locator('.c-fault-mgmt__list-header-sortButton select').selectOption(sort); await page.locator('.c-fault-mgmt__list-header-sortButton select').selectOption(sort);
} }
/** /**
* @param {import('@playwright/test').Page} page * @param {import('@playwright/test').Page} page
*/ */
async function enterSearchTerm(page, term) { async function enterSearchTerm(page, term) {
await page.locator('.c-fault-mgmt-search [aria-label="Search Input"]').fill(term); await page.locator('.c-fault-mgmt-search [aria-label="Search Input"]').fill(term);
} }
/** /**
* @param {import('@playwright/test').Page} page * @param {import('@playwright/test').Page} page
*/ */
async function clearSearch(page) { async function clearSearch(page) {
await enterSearchTerm(page, ''); await enterSearchTerm(page, '');
} }
/** /**
* @param {import('@playwright/test').Page} page * @param {import('@playwright/test').Page} page
*/ */
async function selectFaultItem(page, rowNumber) { async function selectFaultItem(page, rowNumber) {
await page.locator(`.c-fault-mgmt-item > input >> nth=${rowNumber - 1}`).check(); await page.locator(`.c-fault-mgmt-item > input >> nth=${rowNumber - 1}`).check();
} }
/** /**
* @param {import('@playwright/test').Page} page * @param {import('@playwright/test').Page} page
*/ */
async function getHighestSeverity(page) { async function getHighestSeverity(page) {
const criticalCount = await page.locator('[title=CRITICAL]').count(); const criticalCount = await page.locator('[title=CRITICAL]').count();
const warningCount = await page.locator('[title=WARNING]').count(); const warningCount = await page.locator('[title=WARNING]').count();
if (criticalCount > 0) { if (criticalCount > 0) {
return 'CRITICAL'; return 'CRITICAL';
} else if (warningCount > 0) { } else if (warningCount > 0) {
return 'WARNING'; return 'WARNING';
} }
return 'WATCH'; return 'WATCH';
} }
/** /**
* @param {import('@playwright/test').Page} page * @param {import('@playwright/test').Page} page
*/ */
async function getLowestSeverity(page) { async function getLowestSeverity(page) {
const warningCount = await page.locator('[title=WARNING]').count(); const warningCount = await page.locator('[title=WARNING]').count();
const watchCount = await page.locator('[title=WATCH]').count(); const watchCount = await page.locator('[title=WATCH]').count();
if (watchCount > 0) { if (watchCount > 0) {
return 'WATCH'; return 'WATCH';
} else if (warningCount > 0) { } else if (warningCount > 0) {
return 'WARNING'; return 'WARNING';
} }
return 'CRITICAL'; return 'CRITICAL';
} }
/** /**
* @param {import('@playwright/test').Page} page * @param {import('@playwright/test').Page} page
*/ */
async function getFaultResultCount(page) { async function getFaultResultCount(page) {
const count = await page.locator('.c-faults-list-view-item-body > .c-fault-mgmt__list').count(); const count = await page.locator('.c-faults-list-view-item-body > .c-fault-mgmt__list').count();
return count; return count;
} }
/** /**
* @param {import('@playwright/test').Page} page * @param {import('@playwright/test').Page} page
*/ */
function getFault(page, rowNumber) { function getFault(page, rowNumber) {
const fault = page.locator(`.c-faults-list-view-item-body > .c-fault-mgmt__list >> nth=${rowNumber - 1}`); const fault = page.locator(
`.c-faults-list-view-item-body > .c-fault-mgmt__list >> nth=${rowNumber - 1}`
);
return fault; return fault;
} }
/** /**
* @param {import('@playwright/test').Page} page * @param {import('@playwright/test').Page} page
*/ */
function getFaultByName(page, name) { function getFaultByName(page, name) {
const fault = page.locator(`.c-fault-mgmt__list-faultname:has-text("${name}")`); const fault = page.locator(`.c-fault-mgmt__list-faultname:has-text("${name}")`);
return fault; return fault;
} }
/** /**
* @param {import('@playwright/test').Page} page * @param {import('@playwright/test').Page} page
*/ */
async function getFaultName(page, rowNumber) { async function getFaultName(page, rowNumber) {
const faultName = await page.locator(`.c-fault-mgmt__list-faultname >> nth=${rowNumber - 1}`).textContent(); const faultName = await page
.locator(`.c-fault-mgmt__list-faultname >> nth=${rowNumber - 1}`)
.textContent();
return faultName; return faultName;
} }
/** /**
* @param {import('@playwright/test').Page} page * @param {import('@playwright/test').Page} page
*/ */
async function getFaultSeverity(page, rowNumber) { async function getFaultSeverity(page, rowNumber) {
const faultSeverity = await page.locator(`.c-faults-list-view-item-body .c-fault-mgmt__list-severity >> nth=${rowNumber - 1}`).getAttribute('title'); const faultSeverity = await page
.locator(`.c-faults-list-view-item-body .c-fault-mgmt__list-severity >> nth=${rowNumber - 1}`)
.getAttribute('title');
return faultSeverity; return faultSeverity;
} }
/** /**
* @param {import('@playwright/test').Page} page * @param {import('@playwright/test').Page} page
*/ */
async function getFaultNamespace(page, rowNumber) { async function getFaultNamespace(page, rowNumber) {
const faultNamespace = await page.locator(`.c-fault-mgmt__list-path >> nth=${rowNumber - 1}`).textContent(); const faultNamespace = await page
.locator(`.c-fault-mgmt__list-path >> nth=${rowNumber - 1}`)
.textContent();
return faultNamespace; return faultNamespace;
} }
/** /**
* @param {import('@playwright/test').Page} page * @param {import('@playwright/test').Page} page
*/ */
async function getFaultTriggerTime(page, rowNumber) { async function getFaultTriggerTime(page, rowNumber) {
const faultTriggerTime = await page.locator(`.c-fault-mgmt__list-trigTime >> nth=${rowNumber - 1} >> .c-fault-mgmt-item__value`).textContent(); const faultTriggerTime = await page
.locator(`.c-fault-mgmt__list-trigTime >> nth=${rowNumber - 1} >> .c-fault-mgmt-item__value`)
.textContent();
return faultTriggerTime.toString().trim(); return faultTriggerTime.toString().trim();
} }
/** /**
* @param {import('@playwright/test').Page} page * @param {import('@playwright/test').Page} page
*/ */
async function openFaultRowMenu(page, rowNumber) { async function openFaultRowMenu(page, rowNumber) {
// select // select
await page.locator(`.c-fault-mgmt-item > .c-fault-mgmt__list-action-button >> nth=${rowNumber - 1}`).click(); await page
.locator(`.c-fault-mgmt-item > .c-fault-mgmt__list-action-button >> nth=${rowNumber - 1}`)
.click();
} }
// eslint-disable-next-line no-undef // eslint-disable-next-line no-undef
module.exports = { module.exports = {
navigateToFaultManagementWithExample, navigateToFaultManagementWithExample,
navigateToFaultManagementWithStaticExample, navigateToFaultManagementWithStaticExample,
navigateToFaultManagementWithoutExample, navigateToFaultManagementWithoutExample,
navigateToFaultItemInTree, navigateToFaultItemInTree,
acknowledgeFault, acknowledgeFault,
shelveMultipleFaults, shelveMultipleFaults,
acknowledgeMultipleFaults, acknowledgeMultipleFaults,
shelveFault, shelveFault,
changeViewTo, changeViewTo,
sortFaultsBy, sortFaultsBy,
enterSearchTerm, enterSearchTerm,
clearSearch, clearSearch,
selectFaultItem, selectFaultItem,
getHighestSeverity, getHighestSeverity,
getLowestSeverity, getLowestSeverity,
getFaultResultCount, getFaultResultCount,
getFault, getFault,
getFaultByName, getFaultByName,
getFaultName, getFaultName,
getFaultSeverity, getFaultSeverity,
getFaultNamespace, getFaultNamespace,
getFaultTriggerTime, getFaultTriggerTime,
openFaultRowMenu openFaultRowMenu
}; };

View File

@ -28,29 +28,29 @@ const NOTEBOOK_DROP_AREA = '.c-notebook__drag-area';
* @param {import('@playwright/test').Page} page * @param {import('@playwright/test').Page} page
*/ */
async function enterTextEntry(page, text) { async function enterTextEntry(page, text) {
// Click the 'Add Notebook Entry' area // Click the 'Add Notebook Entry' area
await page.locator(NOTEBOOK_DROP_AREA).click(); await page.locator(NOTEBOOK_DROP_AREA).click();
// enter text // enter text
await page.locator('[aria-label="Notebook Entry"].is-selected div.c-ne__text').fill(text); await page.locator('[aria-label="Notebook Entry"].is-selected div.c-ne__text').fill(text);
await commitEntry(page); await commitEntry(page);
} }
/** /**
* @param {import('@playwright/test').Page} page * @param {import('@playwright/test').Page} page
*/ */
async function dragAndDropEmbed(page, notebookObject) { async function dragAndDropEmbed(page, notebookObject) {
// Create example telemetry object // Create example telemetry object
const swg = await createDomainObjectWithDefaults(page, { const swg = await createDomainObjectWithDefaults(page, {
type: "Sine Wave Generator" type: 'Sine Wave Generator'
}); });
// 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.click('button[title="Show selected item in tree"]');
// 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);
} }
/** /**
@ -58,12 +58,12 @@ async function dragAndDropEmbed(page, notebookObject) {
* @param {import('@playwright/test').Page} page * @param {import('@playwright/test').Page} page
*/ */
async function commitEntry(page) { async function commitEntry(page) {
//Click the Commit Entry button //Click the Commit Entry button
await page.locator('.c-ne__save-button > button').click(); await page.locator('.c-ne__save-button > button').click();
} }
// eslint-disable-next-line no-undef // eslint-disable-next-line no-undef
module.exports = { module.exports = {
enterTextEntry, enterTextEntry,
dragAndDropEmbed dragAndDropEmbed
}; };

View File

@ -32,46 +32,53 @@ import { expect } from '../pluginFixtures';
* @param {string} objectUrl The URL of the object to assert against (plan or gantt chart) * @param {string} objectUrl The URL of the object to assert against (plan or gantt chart)
*/ */
export async function assertPlanActivities(page, plan, objectUrl) { export async function assertPlanActivities(page, plan, objectUrl) {
const groups = Object.keys(plan); const groups = Object.keys(plan);
for (const group of groups) { for (const group of groups) {
for (let i = 0; i < plan[group].length; i++) { for (let i = 0; i < plan[group].length; i++) {
// Set the startBound to the start time of the first activity in the group // Set the startBound to the start time of the first activity in the group
const startBound = plan[group][0].start; const startBound = plan[group][0].start;
// Set the endBound to the end time of the current activity // Set the endBound to the end time of the current activity
let endBound = plan[group][i].end; let endBound = plan[group][i].end;
if (endBound === startBound) { if (endBound === startBound) {
// Prevent oddities with setting start and end bound equal // Prevent oddities with setting start and end bound equal
// via URL params // via URL params
endBound += 1; endBound += 1;
} }
// 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(`${objectUrl}?tc.mode=fixed&tc.startBound=${startBound}&tc.endBound=${endBound}&tc.timeSystem=utc&view=plan.view`); await page.goto(
`${objectUrl}?tc.mode=fixed&tc.startBound=${startBound}&tc.endBound=${endBound}&tc.timeSystem=utc&view=plan.view`
);
// Assert that the number of activities in the plan view matches the number of // Assert that the number of activities in the plan view matches the number of
// activities in the plan data within the specified time bounds // activities in the plan data within the specified time bounds
const eventCount = await page.locator('.activity-bounds').count(); const eventCount = await page.locator('.activity-bounds').count();
expect(eventCount).toEqual(Object.values(plan) expect(eventCount).toEqual(
.flat() Object.values(plan)
.filter(event => .flat()
activitiesWithinTimeBounds(event.start, event.end, startBound, endBound)).length); .filter((event) =>
} activitiesWithinTimeBounds(event.start, event.end, startBound, endBound)
).length
);
} }
}
} }
/** /**
* Returns true if the activities time bounds overlap, false otherwise. * Returns true if the activities time bounds overlap, false otherwise.
* @param {number} start1 the start time of the first activity * @param {number} start1 the start time of the first activity
* @param {number} end1 the end time of the first activity * @param {number} end1 the end time of the first activity
* @param {number} start2 the start time of the second activity * @param {number} start2 the start time of the second activity
* @param {number} end2 the end time of the second activity * @param {number} end2 the end time of the second activity
* @returns {boolean} true if the activities overlap, false otherwise * @returns {boolean} true if the activities overlap, false otherwise
*/ */
function activitiesWithinTimeBounds(start1, end1, start2, end2) { function activitiesWithinTimeBounds(start1, end1, start2, end2) {
return (start1 >= start2 && start1 <= end2) return (
|| (end1 >= start2 && end1 <= end2) (start1 >= start2 && start1 <= end2) ||
|| (start2 >= start1 && start2 <= end1) (end1 >= start2 && end1 <= end2) ||
|| (end2 >= start1 && end2 <= end1); (start2 >= start1 && start2 <= end1) ||
(end2 >= start1 && end2 <= end1)
);
} }
/** /**
@ -82,11 +89,13 @@ function activitiesWithinTimeBounds(start1, end1, start2, end2) {
* @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(); 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 = Math.min(...activities.map((activity) => activity.start));
// Get the latest end value // Get the latest end value
const end = Math.max(...activities.map(activity => activity.end)); const end = Math.max(...activities.map((activity) => activity.end));
// 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(`${planObjectUrl}?tc.mode=fixed&tc.startBound=${start}&tc.endBound=${end}&tc.timeSystem=utc&view=plan.view`); await page.goto(
`${planObjectUrl}?tc.mode=fixed&tc.startBound=${start}&tc.endBound=${end}&tc.timeSystem=utc&view=plan.view`
);
} }

View File

@ -25,6 +25,6 @@
// await page.addInitScript({ path: path.join(__dirname, 'useSnowTheme.js') }); // await page.addInitScript({ path: path.join(__dirname, 'useSnowTheme.js') });
document.addEventListener('DOMContentLoaded', () => { document.addEventListener('DOMContentLoaded', () => {
const openmct = window.openmct; const openmct = window.openmct;
openmct.install(openmct.plugins.Snow()); openmct.install(openmct.plugins.Snow());
}); });

View File

@ -9,74 +9,77 @@ const NUM_WORKERS = 2;
/** @type {import('@playwright/test').PlaywrightTestConfig} */ /** @type {import('@playwright/test').PlaywrightTestConfig} */
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',
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: {
command: 'npm run start:coverage', command: 'npm run start:coverage',
url: 'http://localhost:8080/#', url: 'http://localhost:8080/#',
timeout: 200 * 1000, timeout: 200 * 1000,
reuseExistingServer: false reuseExistingServer: false
},
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: 'chrome',
testMatch: '**/*.e2e.spec.js', // only run e2e tests
use: {
browserName: 'chromium'
}
}, },
maxFailures: MAX_FAILURES, //Limits failures to 5 to reduce CI Waste {
workers: NUM_WORKERS, //Limit to 2 for CircleCI Agent name: 'MMOC',
use: { testMatch: '**/*.e2e.spec.js', // only run e2e tests
baseURL: 'http://localhost:8080/', grepInvert: /@snapshot/,
headless: true, use: {
ignoreHTTPSErrors: true, browserName: 'chromium',
screenshot: 'only-on-failure', viewport: {
trace: 'on-first-retry', width: 2560,
video: 'off' height: 1440
},
projects: [
{
name: 'chrome',
testMatch: '**/*.e2e.spec.js', // only run e2e tests
use: {
browserName: 'chromium'
}
},
{
name: 'MMOC',
testMatch: '**/*.e2e.spec.js', // only run e2e tests
grepInvert: /@snapshot/,
use: {
browserName: 'chromium',
viewport: {
width: 2560,
height: 1440
}
}
},
{
name: 'firefox',
testMatch: '**/*.e2e.spec.js', // only run e2e tests
grepInvert: /@snapshot/,
use: {
browserName: 'firefox'
}
},
{
name: 'chrome-beta', //Only Chrome Beta is available on ubuntu -- not chrome canary
testMatch: '**/*.e2e.spec.js', // only run e2e tests
grepInvert: /@snapshot/,
use: {
browserName: 'chromium',
channel: 'chrome-beta'
}
} }
}
},
{
name: 'firefox',
testMatch: '**/*.e2e.spec.js', // only run e2e tests
grepInvert: /@snapshot/,
use: {
browserName: 'firefox'
}
},
{
name: 'chrome-beta', //Only Chrome Beta is available on ubuntu -- not chrome canary
testMatch: '**/*.e2e.spec.js', // only run e2e tests
grepInvert: /@snapshot/,
use: {
browserName: 'chromium',
channel: 'chrome-beta'
}
}
],
reporter: [
['list'],
[
'html',
{
open: 'never',
outputFolder: '../html-test-results' //Must be in different location due to https://github.com/microsoft/playwright/issues/12840
}
], ],
reporter: [ ['junit', { outputFile: '../test-results/results.xml' }],
['list'], ['github'],
['html', { ['@deploysentinel/playwright']
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' }],
['github'],
['@deploysentinel/playwright']
]
}; };
module.exports = config; module.exports = config;

View File

@ -7,98 +7,101 @@ 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',
testIgnore: '**/*.perf.spec.js', testIgnore: '**/*.perf.spec.js',
timeout: 30 * 1000, timeout: 30 * 1000,
webServer: { webServer: {
command: 'npm run start:coverage', command: 'npm run start:coverage',
url: 'http://localhost:8080/#', url: 'http://localhost:8080/#',
timeout: 120 * 1000, timeout: 120 * 1000,
reuseExistingServer: true reuseExistingServer: true
},
workers: 1,
use: {
browserName: 'chromium',
baseURL: 'http://localhost:8080/',
headless: false,
ignoreHTTPSErrors: true,
screenshot: 'only-on-failure',
trace: 'retain-on-failure',
video: 'off'
},
projects: [
{
name: 'chrome',
use: {
browserName: 'chromium'
}
}, },
workers: 1, {
use: { name: 'MMOC',
browserName: "chromium", testMatch: '**/*.e2e.spec.js', // only run e2e tests
baseURL: 'http://localhost:8080/', grepInvert: /@snapshot/,
headless: false, use: {
ignoreHTTPSErrors: true, browserName: 'chromium',
screenshot: 'only-on-failure', viewport: {
trace: 'retain-on-failure', width: 2560,
video: 'off' height: 1440
},
projects: [
{
name: 'chrome',
use: {
browserName: 'chromium'
}
},
{
name: 'MMOC',
testMatch: '**/*.e2e.spec.js', // only run e2e tests
grepInvert: /@snapshot/,
use: {
browserName: 'chromium',
viewport: {
width: 2560,
height: 1440
}
}
},
{
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/,
use: {
browserName: 'webkit'
}
},
{
name: 'firefox',
testMatch: '**/*.e2e.spec.js', // only run e2e tests
grepInvert: /@snapshot/,
use: {
browserName: 'firefox'
}
},
{
name: 'canary',
testMatch: '**/*.e2e.spec.js', // only run e2e tests
grepInvert: /@snapshot/,
use: {
browserName: 'chromium',
channel: 'chrome-canary' //Note this is not available in ubuntu/CircleCI
}
},
{
name: 'chrome-beta',
testMatch: '**/*.e2e.spec.js', // only run e2e tests
grepInvert: /@snapshot/,
use: {
browserName: 'chromium',
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: [ },
['list'], {
['html', { name: 'safari',
open: 'on-failure', testMatch: '**/*.e2e.spec.js', // only run e2e tests
outputFolder: '../html-test-results' //Must be in different location due to https://github.com/microsoft/playwright/issues/12840 grep: /@ipad/, // only run ipad tests due to this bug https://github.com/microsoft/playwright/issues/8340
}] grepInvert: /@snapshot/,
use: {
browserName: 'webkit'
}
},
{
name: 'firefox',
testMatch: '**/*.e2e.spec.js', // only run e2e tests
grepInvert: /@snapshot/,
use: {
browserName: 'firefox'
}
},
{
name: 'canary',
testMatch: '**/*.e2e.spec.js', // only run e2e tests
grepInvert: /@snapshot/,
use: {
browserName: 'chromium',
channel: 'chrome-canary' //Note this is not available in ubuntu/CircleCI
}
},
{
name: 'chrome-beta',
testMatch: '**/*.e2e.spec.js', // only run e2e tests
grepInvert: /@snapshot/,
use: {
browserName: 'chromium',
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: [
['list'],
[
'html',
{
open: 'on-failure',
outputFolder: '../html-test-results' //Must be in different location due to https://github.com/microsoft/playwright/issues/12840
}
] ]
]
}; };
module.exports = config; module.exports = config;

View File

@ -6,38 +6,38 @@ const CI = process.env.CI === 'true';
/** @type {import('@playwright/test').PlaywrightTestConfig} */ /** @type {import('@playwright/test').PlaywrightTestConfig} */
const config = { const config = {
retries: 1, //Only for debugging purposes for trace: 'on-first-retry' retries: 1, //Only for debugging purposes for trace: 'on-first-retry'
testDir: 'tests/performance/', testDir: 'tests/performance/',
timeout: 60 * 1000, timeout: 60 * 1000,
workers: 1, //Only run in serial with 1 worker workers: 1, //Only run in serial with 1 worker
webServer: { webServer: {
command: 'npm run start', //coverage not generated command: 'npm run start', //coverage not generated
url: 'http://localhost:8080/#', url: 'http://localhost:8080/#',
timeout: 200 * 1000, timeout: 200 * 1000,
reuseExistingServer: !CI reuseExistingServer: !CI
}, },
use: { use: {
browserName: "chromium", browserName: 'chromium',
baseURL: 'http://localhost:8080/', baseURL: 'http://localhost:8080/',
headless: CI, //Only if running locally headless: CI, //Only if running locally
ignoreHTTPSErrors: true, ignoreHTTPSErrors: true,
screenshot: 'off', screenshot: 'off',
trace: 'on-first-retry', trace: 'on-first-retry',
video: 'off' video: 'off'
}, },
projects: [ projects: [
{ {
name: 'chrome', name: 'chrome',
use: { use: {
browserName: 'chromium' browserName: 'chromium'
} }
} }
], ],
reporter: [ reporter: [
['list'], ['list'],
['junit', { outputFile: '../test-results/results.xml' }], ['junit', { outputFile: '../test-results/results.xml' }],
['json', { outputFile: '../test-results/results.json' }] ['json', { outputFile: '../test-results/results.json' }]
] ]
}; };
module.exports = config; module.exports = config;

View File

@ -4,48 +4,51 @@
/** @type {import('@playwright/test').PlaywrightTestConfig<{ theme: string }>} */ /** @type {import('@playwright/test').PlaywrightTestConfig<{ theme: string }>} */
const config = { const config = {
retries: 0, // Visual tests should never retry due to snapshot comparison errors. Leaving as a shim retries: 0, // Visual tests should never retry due to snapshot comparison errors. Leaving as a shim
testDir: 'tests/visual', testDir: 'tests/visual',
testMatch: '**/*.visual.spec.js', // only run visual tests testMatch: '**/*.visual.spec.js', // only run visual tests
timeout: 60 * 1000, timeout: 60 * 1000,
workers: 1, //Lower stress on Circle CI Agent for Visual tests https://github.com/percy/cli/discussions/1067 workers: 1, //Lower stress on Circle CI Agent for Visual tests https://github.com/percy/cli/discussions/1067
webServer: { webServer: {
command: 'npm run start:coverage', command: 'npm run start:coverage',
url: 'http://localhost:8080/#', url: 'http://localhost:8080/#',
timeout: 200 * 1000, timeout: 200 * 1000,
reuseExistingServer: !process.env.CI reuseExistingServer: !process.env.CI
},
use: {
baseURL: 'http://localhost:8080/',
headless: true, // this needs to remain headless to avoid visual changes due to GPU rendering in headed browsers
ignoreHTTPSErrors: true,
screenshot: 'only-on-failure',
trace: 'on-first-retry',
video: 'off'
},
projects: [
{
name: 'chrome',
use: {
browserName: 'chromium'
}
}, },
use: { {
baseURL: 'http://localhost:8080/', name: 'chrome-snow-theme', //Runs the same visual tests but with snow-theme enabled
headless: true, // this needs to remain headless to avoid visual changes due to GPU rendering in headed browsers use: {
ignoreHTTPSErrors: true, browserName: 'chromium',
screenshot: 'only-on-failure', theme: 'snow'
trace: 'on-first-retry', }
video: 'off' }
}, ],
projects: [ reporter: [
{ ['list'],
name: 'chrome', ['junit', { outputFile: '../test-results/results.xml' }],
use: { [
browserName: 'chromium' 'html',
} {
}, open: 'on-failure',
{ outputFolder: '../html-test-results' //Must be in different location due to https://github.com/microsoft/playwright/issues/12840
name: 'chrome-snow-theme', //Runs the same visual tests but with snow-theme enabled }
use: {
browserName: 'chromium',
theme: 'snow'
}
}
],
reporter: [
['list'],
['junit', { outputFile: '../test-results/results.xml' }],
['html', {
open: 'on-failure',
outputFolder: '../html-test-results' //Must be in different location due to https://github.com/microsoft/playwright/issues/12840
}]
] ]
]
}; };
module.exports = config; module.exports = config;

View File

@ -120,34 +120,31 @@ const theme = 'espresso';
* *
* @type {string} * @type {string}
*/ */
const myItemsFolderName = "My Items"; const myItemsFolderName = 'My Items';
exports.test = test.extend({ exports.test = 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
page: async ({ page, theme }, use, testInfo) => { page: async ({ page, theme }, use, testInfo) => {
// 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: path.join(__dirname, './helper', './useSnowTheme.js') });
}
// Attach info about the currently running test and its project.
// This will be used by appActions to fill in the created
// domain object's notes.
page.testNotes = [
`${testInfo.titlePath.join('\n')}`,
`${testInfo.project.name}`
].join('\n');
await use(page);
},
myItemsFolderName: [myItemsFolderName, { option: true }],
// eslint-disable-next-line no-shadow
openmctConfig: async ({ myItemsFolderName }, use) => {
await use({ myItemsFolderName });
} }
// Attach info about the currently running test and its project.
// This will be used by appActions to fill in the created
// domain object's notes.
page.testNotes = [`${testInfo.titlePath.join('\n')}`, `${testInfo.project.name}`].join('\n');
await use(page);
},
myItemsFolderName: [myItemsFolderName, { option: true }],
// eslint-disable-next-line no-shadow
openmctConfig: async ({ myItemsFolderName }, use) => {
await use({ myItemsFolderName });
}
}); });
exports.expect = expect; exports.expect = expect;
@ -157,10 +154,10 @@ exports.expect = expect;
* @return {Promise<String>} the stringified stream * @return {Promise<String>} the stringified stream
*/ */
exports.streamToString = async function (readable) { exports.streamToString = async function (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;
}; };

View File

@ -274,10 +274,7 @@
"id": "ac0d7eb1-b485-458f-bd2a-a63aa87a3a8a" "id": "ac0d7eb1-b485-458f-bd2a-a63aa87a3a8a"
} }
], ],
"layoutGrid": [ "layoutGrid": [10, 10],
10,
10
],
"objectStyles": { "objectStyles": {
"ed63cc29-80e2-4e2b-a472-3d6d4adbf310": { "ed63cc29-80e2-4e2b-a472-3d6d4adbf310": {
"staticStyle": { "staticStyle": {
@ -1455,9 +1452,7 @@
"id": "64e49fe7-5b36-43db-8347-4550b910de4c", "id": "64e49fe7-5b36-43db-8347-4550b910de4c",
"telemetry": "any", "telemetry": "any",
"operation": "greaterThan", "operation": "greaterThan",
"input": [ "input": ["120"],
"120"
],
"metadata": "sin" "metadata": "sin"
} }
] ]
@ -1475,10 +1470,7 @@
"id": "59f1c4bf-5d36-450c-9668-6546955fc066", "id": "59f1c4bf-5d36-450c-9668-6546955fc066",
"telemetry": "any", "telemetry": "any",
"operation": "between", "operation": "between",
"input": [ "input": ["120", "-20"],
"120",
"-20"
],
"metadata": "sin" "metadata": "sin"
} }
] ]
@ -1496,9 +1488,7 @@
"id": "6707be12-6a6e-4535-bb97-ab5c86f99934", "id": "6707be12-6a6e-4535-bb97-ab5c86f99934",
"telemetry": "any", "telemetry": "any",
"operation": "lessThan", "operation": "lessThan",
"input": [ "input": ["-20"],
"-20"
],
"metadata": "sin" "metadata": "sin"
} }
] ]
@ -1550,9 +1540,7 @@
"id": "64e49fe7-5b36-43db-8347-4550b910de4c", "id": "64e49fe7-5b36-43db-8347-4550b910de4c",
"telemetry": "any", "telemetry": "any",
"operation": "greaterThan", "operation": "greaterThan",
"input": [ "input": ["120"],
"120"
],
"metadata": "sin" "metadata": "sin"
} }
] ]
@ -1570,10 +1558,7 @@
"id": "59f1c4bf-5d36-450c-9668-6546955fc066", "id": "59f1c4bf-5d36-450c-9668-6546955fc066",
"telemetry": "any", "telemetry": "any",
"operation": "between", "operation": "between",
"input": [ "input": ["120", "-20"],
"120",
"-20"
],
"metadata": "sin" "metadata": "sin"
} }
] ]
@ -1591,9 +1576,7 @@
"id": "6707be12-6a6e-4535-bb97-ab5c86f99934", "id": "6707be12-6a6e-4535-bb97-ab5c86f99934",
"telemetry": "any", "telemetry": "any",
"operation": "lessThan", "operation": "lessThan",
"input": [ "input": ["-20"],
"-20"
],
"metadata": "sin" "metadata": "sin"
} }
] ]
@ -1645,9 +1628,7 @@
"id": "64e49fe7-5b36-43db-8347-4550b910de4c", "id": "64e49fe7-5b36-43db-8347-4550b910de4c",
"telemetry": "any", "telemetry": "any",
"operation": "greaterThan", "operation": "greaterThan",
"input": [ "input": ["150"],
"150"
],
"metadata": "sin" "metadata": "sin"
} }
] ]
@ -1665,10 +1646,7 @@
"id": "59f1c4bf-5d36-450c-9668-6546955fc066", "id": "59f1c4bf-5d36-450c-9668-6546955fc066",
"telemetry": "any", "telemetry": "any",
"operation": "between", "operation": "between",
"input": [ "input": ["50", "-50"],
"50",
"-50"
],
"metadata": "sin" "metadata": "sin"
} }
] ]
@ -1720,9 +1698,7 @@
"id": "64e49fe7-5b36-43db-8347-4550b910de4c", "id": "64e49fe7-5b36-43db-8347-4550b910de4c",
"telemetry": "any", "telemetry": "any",
"operation": "greaterThan", "operation": "greaterThan",
"input": [ "input": ["150"],
"150"
],
"metadata": "sin" "metadata": "sin"
} }
] ]
@ -1740,10 +1716,7 @@
"id": "59f1c4bf-5d36-450c-9668-6546955fc066", "id": "59f1c4bf-5d36-450c-9668-6546955fc066",
"telemetry": "any", "telemetry": "any",
"operation": "between", "operation": "between",
"input": [ "input": ["50", "-50"],
"50",
"-50"
],
"metadata": "sin" "metadata": "sin"
} }
] ]

View File

@ -1 +1,90 @@
{"openmct":{"b3cee102-86dd-4c0a-8eec-4d5d276f8691":{"identifier":{"key":"b3cee102-86dd-4c0a-8eec-4d5d276f8691","namespace":""},"name":"Performance Display Layout","type":"layout","composition":[{"key":"9666e7b4-be0c-47a5-94b8-99accad7155e","namespace":""}],"configuration":{"items":[{"width":32,"height":18,"x":12,"y":9,"identifier":{"key":"9666e7b4-be0c-47a5-94b8-99accad7155e","namespace":""},"hasFrame":true,"fontSize":"default","font":"default","type":"subobject-view","id":"23ca351d-a67d-46aa-a762-290eb742d2f1"}],"layoutGrid":[10,10]},"modified":1654299875432,"location":"mine","persisted":1654299878751},"9666e7b4-be0c-47a5-94b8-99accad7155e":{"identifier":{"key":"9666e7b4-be0c-47a5-94b8-99accad7155e","namespace":""},"name":"Performance Example Imagery","type":"example.imagery","configuration":{"imageLocation":"","imageLoadDelayInMilliSeconds":20000,"imageSamples":[],"layers":[{"source":"dist/imagery/example-imagery-layer-16x9.png","name":"16:9","visible":false},{"source":"dist/imagery/example-imagery-layer-safe.png","name":"Safe","visible":false},{"source":"dist/imagery/example-imagery-layer-scale.png","name":"Scale","visible":false}]},"telemetry":{"values":[{"name":"Name","key":"name"},{"name":"Time","key":"utc","format":"utc","hints":{"domain":2}},{"name":"Local Time","key":"local","format":"local-format","hints":{"domain":1}},{"name":"Image","key":"url","format":"image","hints":{"image":1},"layers":[{"source":"dist/imagery/example-imagery-layer-16x9.png","name":"16:9"},{"source":"dist/imagery/example-imagery-layer-safe.png","name":"Safe"},{"source":"dist/imagery/example-imagery-layer-scale.png","name":"Scale"}]},{"name":"Image Download Name","key":"imageDownloadName","format":"imageDownloadName","hints":{"imageDownloadName":1}}]},"modified":1654299840077,"location":"b3cee102-86dd-4c0a-8eec-4d5d276f8691","persisted":1654299840078}},"rootId":"b3cee102-86dd-4c0a-8eec-4d5d276f8691"} {
"openmct": {
"b3cee102-86dd-4c0a-8eec-4d5d276f8691": {
"identifier": { "key": "b3cee102-86dd-4c0a-8eec-4d5d276f8691", "namespace": "" },
"name": "Performance Display Layout",
"type": "layout",
"composition": [{ "key": "9666e7b4-be0c-47a5-94b8-99accad7155e", "namespace": "" }],
"configuration": {
"items": [
{
"width": 32,
"height": 18,
"x": 12,
"y": 9,
"identifier": { "key": "9666e7b4-be0c-47a5-94b8-99accad7155e", "namespace": "" },
"hasFrame": true,
"fontSize": "default",
"font": "default",
"type": "subobject-view",
"id": "23ca351d-a67d-46aa-a762-290eb742d2f1"
}
],
"layoutGrid": [10, 10]
},
"modified": 1654299875432,
"location": "mine",
"persisted": 1654299878751
},
"9666e7b4-be0c-47a5-94b8-99accad7155e": {
"identifier": { "key": "9666e7b4-be0c-47a5-94b8-99accad7155e", "namespace": "" },
"name": "Performance Example Imagery",
"type": "example.imagery",
"configuration": {
"imageLocation": "",
"imageLoadDelayInMilliSeconds": 20000,
"imageSamples": [],
"layers": [
{
"source": "dist/imagery/example-imagery-layer-16x9.png",
"name": "16:9",
"visible": false
},
{
"source": "dist/imagery/example-imagery-layer-safe.png",
"name": "Safe",
"visible": false
},
{
"source": "dist/imagery/example-imagery-layer-scale.png",
"name": "Scale",
"visible": false
}
]
},
"telemetry": {
"values": [
{ "name": "Name", "key": "name" },
{ "name": "Time", "key": "utc", "format": "utc", "hints": { "domain": 2 } },
{
"name": "Local Time",
"key": "local",
"format": "local-format",
"hints": { "domain": 1 }
},
{
"name": "Image",
"key": "url",
"format": "image",
"hints": { "image": 1 },
"layers": [
{ "source": "dist/imagery/example-imagery-layer-16x9.png", "name": "16:9" },
{ "source": "dist/imagery/example-imagery-layer-safe.png", "name": "Safe" },
{ "source": "dist/imagery/example-imagery-layer-scale.png", "name": "Scale" }
]
},
{
"name": "Image Download Name",
"key": "imageDownloadName",
"format": "imageDownloadName",
"hints": { "imageDownloadName": 1 }
}
]
},
"modified": 1654299840077,
"location": "b3cee102-86dd-4c0a-8eec-4d5d276f8691",
"persisted": 1654299840078
}
},
"rootId": "b3cee102-86dd-4c0a-8eec-4d5d276f8691"
}

View File

@ -1 +1,96 @@
{"openmct":{"6d2fa9fd-f2aa-461a-a1e1-164ac44bec9d":{"identifier":{"key":"6d2fa9fd-f2aa-461a-a1e1-164ac44bec9d","namespace":""},"name":"Performance Notebook","type":"notebook","configuration":{"defaultSort":"oldest","entries":{"3e31c412-33ba-4757-8ade-e9821f6ba321":{"8c8f6035-631c-45af-8c24-786c60295335":[{"id":"entry-1652815305457","createdOn":1652815305457,"createdBy":"","text":"Existing Entry 1","embeds":[]},{"id":"entry-1652815313465","createdOn":1652815313465,"createdBy":"","text":"Existing Entry 2","embeds":[]},{"id":"entry-1652815399955","createdOn":1652815399955,"createdBy":"","text":"Existing Entry 3","embeds":[]}]}},"imageMigrationVer":"v1","pageTitle":"Page","sections":[{"id":"3e31c412-33ba-4757-8ade-e9821f6ba321","isDefault":false,"isSelected":false,"name":"Section1","pages":[{"id":"8c8f6035-631c-45af-8c24-786c60295335","isDefault":false,"isSelected":false,"name":"Page1","pageTitle":"Page"},{"id":"36555942-c9aa-439c-bbdb-0aaf50db50f5","isDefault":false,"isSelected":false,"name":"Page2","pageTitle":"Page"}],"sectionTitle":"Section"},{"id":"dab0bd1d-2c5a-405c-987f-107123d6189a","isDefault":false,"isSelected":true,"name":"Section2","pages":[{"id":"f625a86a-cb99-4898-8082-80543c8de534","isDefault":false,"isSelected":false,"name":"Page1","pageTitle":"Page"},{"id":"e77ef810-f785-42a7-942e-07e999b79c59","isDefault":false,"isSelected":true,"name":"Page2","pageTitle":"Page"}],"sectionTitle":"Section"}],"sectionTitle":"Section","type":"General","showTime":"0"},"modified":1652815915219,"location":"mine","persisted":1652815915222}},"rootId":"6d2fa9fd-f2aa-461a-a1e1-164ac44bec9d"} {
"openmct": {
"6d2fa9fd-f2aa-461a-a1e1-164ac44bec9d": {
"identifier": { "key": "6d2fa9fd-f2aa-461a-a1e1-164ac44bec9d", "namespace": "" },
"name": "Performance Notebook",
"type": "notebook",
"configuration": {
"defaultSort": "oldest",
"entries": {
"3e31c412-33ba-4757-8ade-e9821f6ba321": {
"8c8f6035-631c-45af-8c24-786c60295335": [
{
"id": "entry-1652815305457",
"createdOn": 1652815305457,
"createdBy": "",
"text": "Existing Entry 1",
"embeds": []
},
{
"id": "entry-1652815313465",
"createdOn": 1652815313465,
"createdBy": "",
"text": "Existing Entry 2",
"embeds": []
},
{
"id": "entry-1652815399955",
"createdOn": 1652815399955,
"createdBy": "",
"text": "Existing Entry 3",
"embeds": []
}
]
}
},
"imageMigrationVer": "v1",
"pageTitle": "Page",
"sections": [
{
"id": "3e31c412-33ba-4757-8ade-e9821f6ba321",
"isDefault": false,
"isSelected": false,
"name": "Section1",
"pages": [
{
"id": "8c8f6035-631c-45af-8c24-786c60295335",
"isDefault": false,
"isSelected": false,
"name": "Page1",
"pageTitle": "Page"
},
{
"id": "36555942-c9aa-439c-bbdb-0aaf50db50f5",
"isDefault": false,
"isSelected": false,
"name": "Page2",
"pageTitle": "Page"
}
],
"sectionTitle": "Section"
},
{
"id": "dab0bd1d-2c5a-405c-987f-107123d6189a",
"isDefault": false,
"isSelected": true,
"name": "Section2",
"pages": [
{
"id": "f625a86a-cb99-4898-8082-80543c8de534",
"isDefault": false,
"isSelected": false,
"name": "Page1",
"pageTitle": "Page"
},
{
"id": "e77ef810-f785-42a7-942e-07e999b79c59",
"isDefault": false,
"isSelected": true,
"name": "Page2",
"pageTitle": "Page"
}
],
"sectionTitle": "Section"
}
],
"sectionTitle": "Section",
"type": "General",
"showTime": "0"
},
"modified": 1652815915219,
"location": "mine",
"persisted": 1652815915222
}
},
"rootId": "6d2fa9fd-f2aa-461a-a1e1-164ac44bec9d"
}

View File

@ -1,44 +1,44 @@
{ {
"Group 1": [ "Group 1": [
{ {
"name": "Past event 1", "name": "Past event 1",
"start": 1660320408000, "start": 1660320408000,
"end": 1660343797000, "end": 1660343797000,
"type": "Group 1", "type": "Group 1",
"color": "orange", "color": "orange",
"textColor": "white" "textColor": "white"
}, },
{ {
"name": "Past event 2", "name": "Past event 2",
"start": 1660406808000, "start": 1660406808000,
"end": 1660429160000, "end": 1660429160000,
"type": "Group 1", "type": "Group 1",
"color": "orange", "color": "orange",
"textColor": "white" "textColor": "white"
}, },
{ {
"name": "Past event 3", "name": "Past event 3",
"start": 1660493208000, "start": 1660493208000,
"end": 1660503981000, "end": 1660503981000,
"type": "Group 1", "type": "Group 1",
"color": "orange", "color": "orange",
"textColor": "white" "textColor": "white"
}, },
{ {
"name": "Past event 4", "name": "Past event 4",
"start": 1660579608000, "start": 1660579608000,
"end": 1660624108000, "end": 1660624108000,
"type": "Group 1", "type": "Group 1",
"color": "orange", "color": "orange",
"textColor": "white" "textColor": "white"
}, },
{ {
"name": "Past event 5", "name": "Past event 5",
"start": 1660666008000, "start": 1660666008000,
"end": 1660681529000, "end": 1660681529000,
"type": "Group 1", "type": "Group 1",
"color": "orange", "color": "orange",
"textColor": "white" "textColor": "white"
} }
] ]
} }

View File

@ -1,38 +1,38 @@
{ {
"Group 1": [ "Group 1": [
{ {
"name": "Group 1 event 1", "name": "Group 1 event 1",
"start": 1650320408000, "start": 1650320408000,
"end": 1660343797000, "end": 1660343797000,
"type": "Group 1", "type": "Group 1",
"color": "orange", "color": "orange",
"textColor": "white" "textColor": "white"
}, },
{ {
"name": "Group 1 event 2", "name": "Group 1 event 2",
"start": 1660005808000, "start": 1660005808000,
"end": 1660429160000, "end": 1660429160000,
"type": "Group 1", "type": "Group 1",
"color": "yellow", "color": "yellow",
"textColor": "white" "textColor": "white"
} }
], ],
"Group 2": [ "Group 2": [
{ {
"name": "Group 2 event 1", "name": "Group 2 event 1",
"start": 1660320408000, "start": 1660320408000,
"end": 1660420408000, "end": 1660420408000,
"type": "Group 2", "type": "Group 2",
"color": "green", "color": "green",
"textColor": "white" "textColor": "white"
}, },
{ {
"name": "Group 2 event 2", "name": "Group 2 event 2",
"start": 1660406808000, "start": 1660406808000,
"end": 1690429160000, "end": 1690429160000,
"type": "Group 2", "type": "Group 2",
"color": "blue", "color": "blue",
"textColor": "white" "textColor": "white"
} }
] ]
} }

View File

@ -21,145 +21,149 @@
*****************************************************************************/ *****************************************************************************/
const { test, expect } = require('../../pluginFixtures.js'); const { test, expect } = require('../../pluginFixtures.js');
const { createDomainObjectWithDefaults, createNotification, expandEntireTree } = require('../../appActions.js'); const {
createDomainObjectWithDefaults,
createNotification,
expandEntireTree
} = require('../../appActions.js');
test.describe('AppActions', () => { test.describe('AppActions', () => {
test('createDomainObjectsWithDefaults', async ({ page }) => { test('createDomainObjectsWithDefaults', async ({ page }) => {
await page.goto('./', { waitUntil: 'domcontentloaded' }); await page.goto('./', { waitUntil: 'domcontentloaded' });
const e2eFolder = await createDomainObjectWithDefaults(page, { const e2eFolder = await createDomainObjectWithDefaults(page, {
type: 'Folder', type: 'Folder',
name: 'e2e folder' name: 'e2e folder'
});
await test.step('Create multiple flat objects in a row', async () => {
const timer1 = await createDomainObjectWithDefaults(page, {
type: 'Timer',
name: 'Timer Foo',
parent: e2eFolder.uuid
});
const timer2 = await createDomainObjectWithDefaults(page, {
type: 'Timer',
name: 'Timer Bar',
parent: e2eFolder.uuid
});
const timer3 = await createDomainObjectWithDefaults(page, {
type: 'Timer',
name: 'Timer Baz',
parent: e2eFolder.uuid
});
await page.goto(timer1.url);
await expect(page.locator('.l-browse-bar__object-name')).toHaveText(timer1.name);
await page.goto(timer2.url);
await expect(page.locator('.l-browse-bar__object-name')).toHaveText(timer2.name);
await page.goto(timer3.url);
await expect(page.locator('.l-browse-bar__object-name')).toHaveText(timer3.name);
});
await test.step('Create multiple nested objects in a row', async () => {
const folder1 = await createDomainObjectWithDefaults(page, {
type: 'Folder',
name: 'Folder Foo',
parent: e2eFolder.uuid
});
const folder2 = await createDomainObjectWithDefaults(page, {
type: 'Folder',
name: 'Folder Bar',
parent: folder1.uuid
});
const folder3 = await createDomainObjectWithDefaults(page, {
type: 'Folder',
name: 'Folder Baz',
parent: folder2.uuid
});
await page.goto(folder1.url);
await expect(page.locator('.l-browse-bar__object-name')).toHaveText(folder1.name);
await page.goto(folder2.url);
await expect(page.locator('.l-browse-bar__object-name')).toHaveText(folder2.name);
await page.goto(folder3.url);
await expect(page.locator('.l-browse-bar__object-name')).toHaveText(folder3.name);
expect(folder1.url).toBe(`${e2eFolder.url}/${folder1.uuid}`);
expect(folder2.url).toBe(`${e2eFolder.url}/${folder1.uuid}/${folder2.uuid}`);
expect(folder3.url).toBe(`${e2eFolder.url}/${folder1.uuid}/${folder2.uuid}/${folder3.uuid}`);
});
}); });
test("createNotification", async ({ page }) => {
await page.goto('./', { waitUntil: 'domcontentloaded' }); await test.step('Create multiple flat objects in a row', async () => {
await createNotification(page, { const timer1 = await createDomainObjectWithDefaults(page, {
message: 'Test info notification', type: 'Timer',
severity: 'info' name: 'Timer Foo',
}); parent: e2eFolder.uuid
await expect(page.locator('.c-message-banner__message')).toHaveText('Test info notification'); });
await expect(page.locator('.c-message-banner')).toHaveClass(/info/); const timer2 = await createDomainObjectWithDefaults(page, {
await page.locator('[aria-label="Dismiss"]').click(); type: 'Timer',
await createNotification(page, { name: 'Timer Bar',
message: 'Test alert notification', parent: e2eFolder.uuid
severity: 'alert' });
}); const timer3 = await createDomainObjectWithDefaults(page, {
await expect(page.locator('.c-message-banner__message')).toHaveText('Test alert notification'); type: 'Timer',
await expect(page.locator('.c-message-banner')).toHaveClass(/alert/); name: 'Timer Baz',
await page.locator('[aria-label="Dismiss"]').click(); parent: e2eFolder.uuid
await createNotification(page, { });
message: 'Test error notification',
severity: 'error' await page.goto(timer1.url);
}); await expect(page.locator('.l-browse-bar__object-name')).toHaveText(timer1.name);
await expect(page.locator('.c-message-banner__message')).toHaveText('Test error notification'); await page.goto(timer2.url);
await expect(page.locator('.c-message-banner')).toHaveClass(/error/); await expect(page.locator('.l-browse-bar__object-name')).toHaveText(timer2.name);
await page.locator('[aria-label="Dismiss"]').click(); await page.goto(timer3.url);
await expect(page.locator('.l-browse-bar__object-name')).toHaveText(timer3.name);
}); });
test('expandEntireTree', async ({ page }) => {
await page.goto('./', { waitUntil: 'domcontentloaded' });
const rootFolder = await createDomainObjectWithDefaults(page, { await test.step('Create multiple nested objects in a row', async () => {
type: 'Folder' const folder1 = await createDomainObjectWithDefaults(page, {
}); type: 'Folder',
const folder1 = await createDomainObjectWithDefaults(page, { name: 'Folder Foo',
type: 'Folder', parent: e2eFolder.uuid
parent: rootFolder.uuid });
}); const folder2 = await createDomainObjectWithDefaults(page, {
type: 'Folder',
name: 'Folder Bar',
parent: folder1.uuid
});
const folder3 = await createDomainObjectWithDefaults(page, {
type: 'Folder',
name: 'Folder Baz',
parent: folder2.uuid
});
await page.goto(folder1.url);
await expect(page.locator('.l-browse-bar__object-name')).toHaveText(folder1.name);
await page.goto(folder2.url);
await expect(page.locator('.l-browse-bar__object-name')).toHaveText(folder2.name);
await page.goto(folder3.url);
await expect(page.locator('.l-browse-bar__object-name')).toHaveText(folder3.name);
await createDomainObjectWithDefaults(page, { expect(folder1.url).toBe(`${e2eFolder.url}/${folder1.uuid}`);
type: 'Clock', expect(folder2.url).toBe(`${e2eFolder.url}/${folder1.uuid}/${folder2.uuid}`);
parent: folder1.uuid expect(folder3.url).toBe(`${e2eFolder.url}/${folder1.uuid}/${folder2.uuid}/${folder3.uuid}`);
});
const folder2 = await createDomainObjectWithDefaults(page, {
type: 'Folder',
parent: folder1.uuid
});
await createDomainObjectWithDefaults(page, {
type: 'Folder',
parent: folder1.uuid
});
await createDomainObjectWithDefaults(page, {
type: 'Display Layout',
parent: folder2.uuid
});
await createDomainObjectWithDefaults(page, {
type: 'Folder',
parent: folder2.uuid
});
await page.goto('./#/browse/mine');
await expandEntireTree(page);
const treePane = page.getByRole('tree', {
name: "Main Tree"
});
const treePaneCollapsedItems = treePane.getByRole('treeitem', { expanded: false });
expect(await treePaneCollapsedItems.count()).toBe(0);
await page.goto('./#/browse/mine');
//Click the Create button
await page.click('button:has-text("Create")');
// Click the object specified by 'type'
await page.click(`li[role='menuitem']:text("Clock")`);
await expandEntireTree(page, "Create Modal Tree");
const locatorTree = page.getByRole("tree", {
name: "Create Modal Tree"
});
const locatorTreeCollapsedItems = locatorTree.locator('role=treeitem[expanded=false]');
expect(await locatorTreeCollapsedItems.count()).toBe(0);
}); });
});
test('createNotification', async ({ page }) => {
await page.goto('./', { waitUntil: 'domcontentloaded' });
await createNotification(page, {
message: 'Test info notification',
severity: 'info'
});
await expect(page.locator('.c-message-banner__message')).toHaveText('Test info notification');
await expect(page.locator('.c-message-banner')).toHaveClass(/info/);
await page.locator('[aria-label="Dismiss"]').click();
await createNotification(page, {
message: 'Test alert notification',
severity: 'alert'
});
await expect(page.locator('.c-message-banner__message')).toHaveText('Test alert notification');
await expect(page.locator('.c-message-banner')).toHaveClass(/alert/);
await page.locator('[aria-label="Dismiss"]').click();
await createNotification(page, {
message: 'Test error notification',
severity: 'error'
});
await expect(page.locator('.c-message-banner__message')).toHaveText('Test error notification');
await expect(page.locator('.c-message-banner')).toHaveClass(/error/);
await page.locator('[aria-label="Dismiss"]').click();
});
test('expandEntireTree', async ({ page }) => {
await page.goto('./', { waitUntil: 'domcontentloaded' });
const rootFolder = await createDomainObjectWithDefaults(page, {
type: 'Folder'
});
const folder1 = await createDomainObjectWithDefaults(page, {
type: 'Folder',
parent: rootFolder.uuid
});
await createDomainObjectWithDefaults(page, {
type: 'Clock',
parent: folder1.uuid
});
const folder2 = await createDomainObjectWithDefaults(page, {
type: 'Folder',
parent: folder1.uuid
});
await createDomainObjectWithDefaults(page, {
type: 'Folder',
parent: folder1.uuid
});
await createDomainObjectWithDefaults(page, {
type: 'Display Layout',
parent: folder2.uuid
});
await createDomainObjectWithDefaults(page, {
type: 'Folder',
parent: folder2.uuid
});
await page.goto('./#/browse/mine');
await expandEntireTree(page);
const treePane = page.getByRole('tree', {
name: 'Main Tree'
});
const treePaneCollapsedItems = treePane.getByRole('treeitem', { expanded: false });
expect(await treePaneCollapsedItems.count()).toBe(0);
await page.goto('./#/browse/mine');
//Click the Create button
await page.click('button:has-text("Create")');
// Click the object specified by 'type'
await page.click(`li[role='menuitem']:text("Clock")`);
await expandEntireTree(page, 'Create Modal Tree');
const locatorTree = page.getByRole('tree', {
name: 'Create Modal Tree'
});
const locatorTreeCollapsedItems = locatorTree.locator('role=treeitem[expanded=false]');
expect(await locatorTreeCollapsedItems.count()).toBe(0);
});
}); });

View File

@ -29,27 +29,25 @@ relates to how we've extended it (i.e. ./e2e/baseFixtures.js) and assumptions ma
const { test } = require('../../baseFixtures.js'); const { test } = require('../../baseFixtures.js');
test.describe('baseFixtures tests', () => { test.describe('baseFixtures tests', () => {
test('Verify that tests fail if console.error is thrown', async ({ page }) => { test('Verify that tests fail if console.error is thrown', async ({ page }) => {
test.fail(); test.fail();
//Go to baseURL //Go to baseURL
await page.goto('./', { waitUntil: 'domcontentloaded' }); await page.goto('./', { waitUntil: 'domcontentloaded' });
//Verify that ../fixtures.js detects console log errors //Verify that ../fixtures.js detects console log errors
await Promise.all([ await Promise.all([
page.evaluate(() => console.error('This should result in a failure')), page.evaluate(() => console.error('This should result in a failure')),
page.waitForEvent('console') // always wait for the event to happen while triggering it! page.waitForEvent('console') // always wait for the event to happen while triggering it!
]); ]);
});
test('Verify that tests pass if console.warn is thrown', async ({ page }) => {
//Go to baseURL
await page.goto('./', { waitUntil: 'domcontentloaded' });
}); //Verify that ../fixtures.js detects console log errors
test('Verify that tests pass if console.warn is thrown', async ({ page }) => { await Promise.all([
//Go to baseURL page.evaluate(() => console.warn('This should result in a pass')),
await page.goto('./', { waitUntil: 'domcontentloaded' }); page.waitForEvent('console') // always wait for the event to happen while triggering it!
]);
//Verify that ../fixtures.js detects console log errors });
await Promise.all([
page.evaluate(() => console.warn('This should result in a pass')),
page.waitForEvent('console') // always wait for the event to happen while triggering it!
]);
});
}); });

View File

@ -21,28 +21,28 @@
*****************************************************************************/ *****************************************************************************/
/* /*
* This test suite template is to be used when creating new test suites. It will be kept up to date with the latest improvements * This test suite template is to be used when creating new test suites. It will be kept up to date with the latest improvements
* made by the Open MCT team. It will also follow our best pratices as those evolve. Please use this structure as a _reference_ and clear * made by the Open MCT team. It will also follow our best pratices as those evolve. Please use this structure as a _reference_ and clear
* or update any references when creating a new test suite! * or update any references when creating a new test suite!
* *
* To illustrate current best practices, we've included a mocked up test suite for Renaming a Timer domain object. * To illustrate current best practices, we've included a mocked up test suite for Renaming a Timer domain object.
* *
* Demonstrated: * Demonstrated:
* - Using appActions to leverage existing functions * - Using appActions to leverage existing functions
* - Structure * - Structure
* - @unstable annotation * - @unstable annotation
* - await, expect, test, describe syntax * - await, expect, test, describe syntax
* - Writing a custom function for a test suite * - Writing a custom function for a test suite
* - Test stub for unfinished test coverage (test.fixme) * - Test stub for unfinished test coverage (test.fixme)
* *
* The structure should follow * The structure should follow
* 1. imports * 1. imports
* 2. test.describe() * 2. test.describe()
* 3. -> test1 * 3. -> test1
* -> test2 * -> test2
* -> test3(stub) * -> test3(stub)
* 4. Any custom functions * 4. Any custom functions
*/ */
// Structure: Some standard Imports. Please update the required pathing. // Structure: Some standard Imports. Please update the required pathing.
const { test, expect } = require('../../pluginFixtures'); const { test, expect } = require('../../pluginFixtures');
@ -58,63 +58,63 @@ const { createDomainObjectWithDefaults } = require('../../appActions');
* as a part of our test promotion pipeline. * as a part of our test promotion pipeline.
*/ */
test.describe('Renaming Timer Object', () => { test.describe('Renaming Timer Object', () => {
// Top-level declaration of the Timer object created in beforeEach(). // Top-level declaration of the Timer object created in beforeEach().
// We can then use this throughout the entire test suite. // We can then use this throughout the entire test suite.
let timer; let timer;
test.beforeEach(async ({ page }) => { test.beforeEach(async ({ page }) => {
// Open a browser, navigate to the main page, and wait until all network events to resolve // Open a browser, navigate to the main page, and wait until all network events to resolve
await page.goto('./', { waitUntil: 'domcontentloaded' }); await page.goto('./', { waitUntil: 'domcontentloaded' });
// We provide some helper functions in appActions like `createDomainObjectWithDefaults()`. // We provide some helper functions in appActions like `createDomainObjectWithDefaults()`.
// This example will create a Timer object with default properties, under the root folder: // This example will create a Timer object with default properties, under the root folder:
timer = await createDomainObjectWithDefaults(page, { type: 'Timer' }); timer = await createDomainObjectWithDefaults(page, { type: 'Timer' });
// Assert the object to be created and check its name in the title // Assert the object to be created and check its name in the title
await expect(page.locator('.l-browse-bar__object-name')).toContainText(timer.name); await expect(page.locator('.l-browse-bar__object-name')).toContainText(timer.name);
}); });
/** /**
* Make sure to use testcase names which are descriptive and easy to understand. * Make sure to use testcase names which are descriptive and easy to understand.
* A good testcase name concisely describes the test's goal(s) and should give * A good testcase name concisely describes the test's goal(s) and should give
* some hint as to what went wrong if the test fails. * some hint as to what went wrong if the test fails.
*/ */
test('An existing Timer object can be renamed via the 3dot actions menu', async ({ page }) => { test('An existing Timer object can be renamed via the 3dot actions menu', async ({ page }) => {
const newObjectName = "Renamed Timer"; const newObjectName = 'Renamed Timer';
// We've created an example of a shared function which pases the page and newObjectName values // We've created an example of a shared function which pases the page and newObjectName values
await renameTimerFrom3DotMenu(page, timer.url, newObjectName); await renameTimerFrom3DotMenu(page, timer.url, newObjectName);
// Assert that the name has changed in the browser bar to the value we assigned above // Assert that the name has changed in the browser bar to the value we assigned above
await expect(page.locator('.l-browse-bar__object-name')).toContainText(newObjectName); await expect(page.locator('.l-browse-bar__object-name')).toContainText(newObjectName);
}); });
test('An existing Timer object can be renamed twice', async ({ page }) => { test('An existing Timer object can be renamed twice', async ({ page }) => {
const newObjectName = "Renamed Timer"; const newObjectName = 'Renamed Timer';
const newObjectName2 = "Re-Renamed Timer"; const newObjectName2 = 'Re-Renamed Timer';
await renameTimerFrom3DotMenu(page, timer.url, newObjectName); await renameTimerFrom3DotMenu(page, timer.url, newObjectName);
// Assert that the name has changed in the browser bar to the value we assigned above // Assert that the name has changed in the browser bar to the value we assigned above
await expect(page.locator('.l-browse-bar__object-name')).toContainText(newObjectName); await expect(page.locator('.l-browse-bar__object-name')).toContainText(newObjectName);
// Rename the Timer object again // Rename the Timer object again
await renameTimerFrom3DotMenu(page, timer.url, newObjectName2); await renameTimerFrom3DotMenu(page, timer.url, newObjectName2);
// Assert that the name has changed in the browser bar to the second value // Assert that the name has changed in the browser bar to the second value
await expect(page.locator('.l-browse-bar__object-name')).toContainText(newObjectName2); await expect(page.locator('.l-browse-bar__object-name')).toContainText(newObjectName2);
}); });
/** /**
* If you run out of time to write new tests, please stub in the missing tests * If you run out of time to write new tests, please stub in the missing tests
* in-place with a test.fixme and BDD-style test steps. * in-place with a test.fixme and BDD-style test steps.
* Someone will carry the baton! * Someone will carry the baton!
*/ */
test.fixme('Can Rename Timer Object from Tree', async ({ page }) => { test.fixme('Can Rename Timer Object from Tree', async ({ page }) => {
//Create a new object //Create a new object
//Copy this object //Copy this object
//Delete first object //Delete first object
//Expect copied object to persist //Expect copied object to persist
}); });
}); });
/** /**
@ -131,18 +131,18 @@ test.describe('Renaming Timer Object', () => {
* @param {string} newNameForTimer New name for object * @param {string} newNameForTimer New name for object
*/ */
async function renameTimerFrom3DotMenu(page, timerUrl, newNameForTimer) { async function renameTimerFrom3DotMenu(page, timerUrl, newNameForTimer) {
// Navigate to the timer object // Navigate to the timer object
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 options"]').click();
// Click text=Edit Properties... // Click text=Edit Properties...
await page.locator('text=Edit Properties...').click(); await page.locator('text=Edit Properties...').click();
// Rename the timer object // Rename the timer object
await page.locator('text=Properties Title Notes >> input[type="text"]').fill(newNameForTimer); await page.locator('text=Properties Title Notes >> input[type="text"]').fill(newNameForTimer);
// Click Ok button to Save // Click Ok button to Save
await page.locator('button:has-text("OK")').click(); await page.locator('button:has-text("OK")').click();
} }

View File

@ -35,30 +35,30 @@ const { createDomainObjectWithDefaults } = require('../../appActions.js');
const { test, expect } = require('../../pluginFixtures.js'); const { test, expect } = require('../../pluginFixtures.js');
test('Generate Visual Test Data @localStorage', async ({ page, context }) => { test('Generate Visual Test Data @localStorage', async ({ page, context }) => {
//Go to baseURL //Go to baseURL
await page.goto('./', { waitUntil: 'domcontentloaded' }); await page.goto('./', { waitUntil: 'domcontentloaded' });
const overlayPlot = await createDomainObjectWithDefaults(page, { type: 'Overlay Plot' }); const overlayPlot = await createDomainObjectWithDefaults(page, { type: 'Overlay Plot' });
// click create button // click create button
await page.locator('button:has-text("Create")').click(); await page.locator('button:has-text("Create")').click();
// add sine wave generator with defaults // add sine wave generator with defaults
await page.locator('li[role="menuitem"]:has-text("Sine Wave Generator")').click(); await page.locator('li[role="menuitem"]:has-text("Sine Wave Generator")').click();
//Add a 5000 ms Delay //Add a 5000 ms Delay
await page.locator('[aria-label="Loading Delay \\(ms\\)"]').fill('5000'); await page.locator('[aria-label="Loading Delay \\(ms\\)"]').fill('5000');
await Promise.all([ await Promise.all([
page.waitForNavigation(), page.waitForNavigation(),
page.locator('button:has-text("OK")').click(), page.locator('button:has-text("OK")').click(),
//Wait for Save Banner to appear //Wait for Save Banner to appear
page.waitForSelector('.c-message-banner__message') page.waitForSelector('.c-message-banner__message')
]); ]);
// focus the overlay plot // focus the overlay plot
await page.goto(overlayPlot.url); await page.goto(overlayPlot.url);
await expect(page.locator('.l-browse-bar__object-name')).toContainText(overlayPlot.name); await expect(page.locator('.l-browse-bar__object-name')).toContainText(overlayPlot.name);
//Save localStorage for future test execution //Save localStorage for future test execution
await context.storageState({ path: './e2e/test-data/VisualTestData_storage.json' }); await context.storageState({ path: './e2e/test-data/VisualTestData_storage.json' });
}); });

View File

@ -29,18 +29,16 @@ const { test } = require('../../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', () => {
// test.use({ domainObjectName: 'Timer' }); // test.use({ domainObjectName: 'Timer' });
// let timerUUID; // let timerUUID;
// test('Creates a timer object @framework @unstable', ({ domainObject }) => {
// test('Creates a timer object @framework @unstable', ({ domainObject }) => { // const { uuid } = domainObject;
// const { uuid } = domainObject; // const uuidRegexp = /[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}/;
// const uuidRegexp = /[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}/; // expect(uuid).toMatch(uuidRegexp);
// expect(uuid).toMatch(uuidRegexp); // timerUUID = uuid;
// timerUUID = uuid; // });
// }); // test('Provides same uuid for subsequent uses of the same object @framework', ({ domainObject }) => {
// const { uuid } = domainObject;
// test('Provides same uuid for subsequent uses of the same object @framework', ({ domainObject }) => { // expect(uuid).toEqual(timerUUID);
// const { uuid } = domainObject; // });
// expect(uuid).toEqual(timerUUID);
// });
}); });

View File

@ -21,16 +21,15 @@
*****************************************************************************/ *****************************************************************************/
/* /*
* This test suite template is to be used when verifying Test Data files found in /e2e/test-data/ * This test suite template is to be used when verifying Test Data files found in /e2e/test-data/
*/ */
const { test } = require('../../baseFixtures'); const { test } = require('../../baseFixtures');
test.describe('recycled_local_storage @localStorage', () => { test.describe('recycled_local_storage @localStorage', () => {
//We may want to do some additional level of verification of this file. For now, we just verify that it exists and can be used in a test suite. //We may want to do some additional level of verification of this file. For now, we just verify that it exists and can be used in a test suite.
test.use({ storageState: './e2e/test-data/recycled_local_storage.json' }); test.use({ storageState: './e2e/test-data/recycled_local_storage.json' });
test('Can use recycled_local_storage file', async ({ page }) => { test('Can use recycled_local_storage file', async ({ page }) => {
await page.goto('./', { waitUntil: 'domcontentloaded' }); await page.goto('./', { waitUntil: 'domcontentloaded' });
}); });
}); });

View File

@ -27,37 +27,39 @@ This test suite is dedicated to tests which verify branding related components.
const { test, expect } = require('../../baseFixtures.js'); const { test, expect } = require('../../baseFixtures.js');
test.describe('Branding tests', () => { test.describe('Branding tests', () => {
test('About Modal launches with basic branding properties', async ({ page }) => { test('About Modal launches with basic branding properties', async ({ page }) => {
// Go to baseURL // Go to baseURL
await page.goto('./', { waitUntil: 'domcontentloaded' }); await page.goto('./', { waitUntil: 'domcontentloaded' });
// Click About button // Click About button
await page.click('.l-shell__app-logo'); await page.click('.l-shell__app-logo');
// Verify that the NASA Logo Appears // Verify that the NASA Logo Appears
await expect(page.locator('.c-about__image')).toBeVisible(); await expect(page.locator('.c-about__image')).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(); const versionInformationLocator = page.locator('ul.t-info.l-info.s-info').first();
await expect(versionInformationLocator).toBeEnabled(); await expect(versionInformationLocator).toBeEnabled();
await expect.soft(versionInformationLocator).toContainText(/Version: \d/); await expect.soft(versionInformationLocator).toContainText(/Version: \d/);
await expect.soft(versionInformationLocator).toContainText(/Build Date: ((?:Mon|Tue|Wed|Thu|Fri|Sat|Sun))/); await expect
await expect.soft(versionInformationLocator).toContainText(/Revision: \b[0-9a-f]{5,40}\b/); .soft(versionInformationLocator)
await expect.soft(versionInformationLocator).toContainText(/Branch: ./); .toContainText(/Build Date: ((?:Mon|Tue|Wed|Thu|Fri|Sat|Sun))/);
}); await expect.soft(versionInformationLocator).toContainText(/Revision: \b[0-9a-f]{5,40}\b/);
test('Verify Links in About Modal @2p', async ({ page }) => { await expect.soft(versionInformationLocator).toContainText(/Branch: ./);
// Go to baseURL });
await page.goto('./', { waitUntil: 'domcontentloaded' }); 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.click('.l-shell__app-logo');
// 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.locator('text=click here for third party licensing information').click()
]); ]);
await page2.waitForLoadState('networkidle'); //Avoids timing issues with juggler/firefox await page2.waitForLoadState('networkidle'); //Avoids timing issues with juggler/firefox
expect(page2.waitForURL('**/licenses**')).toBeTruthy(); expect(page2.waitForURL('**/licenses**')).toBeTruthy();
}); });
}); });

View File

@ -21,91 +21,98 @@
*****************************************************************************/ *****************************************************************************/
/* /*
* This test suite is meant to be executed against a couchdb container. More doc to come * This test suite is meant to be executed against a couchdb container. More doc to come
* *
*/ */
const { test, expect } = require('../../pluginFixtures'); const { test, expect } = require('../../pluginFixtures');
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 });
//TODO BeforeAll Verify CouchDB Connectivity with APIContext //TODO BeforeAll Verify CouchDB Connectivity with APIContext
test('Shows green if connected', async ({ page }) => { test('Shows green if connected', async ({ page }) => {
await page.route('**/openmct/mine', route => { await page.route('**/openmct/mine', (route) => {
route.fulfill({ route.fulfill({
status: 200, status: 200,
contentType: 'application/json', contentType: 'application/json',
body: JSON.stringify({}) body: JSON.stringify({})
}); });
});
//Go to baseURL
await page.goto('./#/browse/mine?hideTree=true&hideInspector=true', { waitUntil: 'networkidle' });
await expect(page.locator('div:has-text("CouchDB is connected")').nth(3)).toBeVisible();
}); });
test('Shows red if not connected', async ({ page }) => {
await page.route('**/openmct/**', route => {
route.fulfill({
status: 503,
contentType: 'application/json',
body: JSON.stringify({})
});
});
//Go to baseURL //Go to baseURL
await page.goto('./#/browse/mine?hideTree=true&hideInspector=true', { waitUntil: 'networkidle' }); await page.goto('./#/browse/mine?hideTree=true&hideInspector=true', {
await expect(page.locator('div:has-text("CouchDB is offline")').nth(3)).toBeVisible(); waitUntil: 'networkidle'
});
await expect(page.locator('div:has-text("CouchDB is connected")').nth(3)).toBeVisible();
});
test('Shows red if not connected', async ({ page }) => {
await page.route('**/openmct/**', (route) => {
route.fulfill({
status: 503,
contentType: 'application/json',
body: JSON.stringify({})
});
}); });
test('Shows unknown if it receives an unexpected response code', async ({ page }) => {
await page.route('**/openmct/mine', route => {
route.fulfill({
status: 418,
contentType: 'application/json',
body: JSON.stringify({})
});
});
//Go to baseURL //Go to baseURL
await page.goto('./#/browse/mine?hideTree=true&hideInspector=true', { waitUntil: 'networkidle' }); await page.goto('./#/browse/mine?hideTree=true&hideInspector=true', {
await expect(page.locator('div:has-text("CouchDB connectivity unknown")').nth(3)).toBeVisible(); waitUntil: 'networkidle'
}); });
await expect(page.locator('div:has-text("CouchDB is offline")').nth(3)).toBeVisible();
});
test('Shows unknown if it receives an unexpected response code', async ({ page }) => {
await page.route('**/openmct/mine', (route) => {
route.fulfill({
status: 418,
contentType: 'application/json',
body: JSON.stringify({})
});
});
//Go to baseURL
await page.goto('./#/browse/mine?hideTree=true&hideInspector=true', {
waitUntil: 'networkidle'
});
await expect(page.locator('div:has-text("CouchDB connectivity unknown")').nth(3)).toBeVisible();
});
}); });
test.describe("CouchDB initialization with mocked responses @couchdb", () => { test.describe('CouchDB initialization with mocked responses @couchdb', () => {
test.use({ failOnConsoleError: false }); test.use({ failOnConsoleError: false });
test("'My Items' folder is created if it doesn't exist", async ({ page }) => { test("'My Items' folder is created if it doesn't exist", async ({ page }) => {
const mockedMissingObjectResponsefromCouchDB = { const mockedMissingObjectResponsefromCouchDB = {
status: 404, status: 404,
contentType: 'application/json', contentType: 'application/json',
body: JSON.stringify({}) body: JSON.stringify({})
}; };
// Override the first request to GET openmct/mine to return a 404. // Override the first request to GET openmct/mine to return a 404.
// This simulates the case of starting Open MCT with a fresh database // This simulates the case of starting Open MCT with a fresh database
// and no "My Items" folder created yet. // and no "My Items" folder created yet.
await page.route('**/mine', route => { await page.route(
route.fulfill(mockedMissingObjectResponsefromCouchDB); '**/mine',
}, { times: 1 }); (route) => {
route.fulfill(mockedMissingObjectResponsefromCouchDB);
},
{ times: 1 }
);
// Set up promise to verify that a PUT request to create "My Items" // Set up promise to verify that a PUT request to create "My Items"
// folder was made. // folder was made.
const putMineFolderRequest = page.waitForRequest(req => const putMineFolderRequest = page.waitForRequest(
req.url().endsWith('/mine') (req) => req.url().endsWith('/mine') && req.method() === 'PUT'
&& req.method() === 'PUT'); );
// Set up promise to verify that a GET request to retrieve "My Items" // Set up promise to verify that a GET request to retrieve "My Items"
// folder was made. // folder was made.
const getMineFolderRequest = page.waitForRequest(req => const getMineFolderRequest = page.waitForRequest(
req.url().endsWith('/mine') (req) => req.url().endsWith('/mine') && req.method() === 'GET'
&& req.method() === 'GET'); );
// Go to baseURL. // Go to baseURL.
await page.goto('./', { waitUntil: 'domcontentloaded' }); await page.goto('./', { waitUntil: 'domcontentloaded' });
// Wait for both requests to resolve. // Wait for both requests to resolve.
await Promise.all([ await Promise.all([putMineFolderRequest, getMineFolderRequest]);
putMineFolderRequest, });
getMineFolderRequest
]);
});
}); });

View File

@ -28,32 +28,31 @@ const { test, expect } = require('../../../pluginFixtures');
const { createDomainObjectWithDefaults } = require('../../../appActions'); const { createDomainObjectWithDefaults } = require('../../../appActions');
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 }) => {
//Go to baseURL //Go to baseURL
await page.goto('./', { waitUntil: 'domcontentloaded' }); await page.goto('./', { waitUntil: 'domcontentloaded' });
//Create a name for the object //Create a name for the object
const newObjectName = 'Test Event Generator'; const newObjectName = 'Test Event Generator';
await createDomainObjectWithDefaults(page, { await createDomainObjectWithDefaults(page, {
type: 'Event Message Generator', type: 'Event Message Generator',
name: newObjectName name: newObjectName
});
//Assertions against newly created object which define standard behavior
await expect(page.waitForURL(/.*&view=table/)).toBeTruthy();
await expect(page.locator('.l-browse-bar__object-name')).toContainText(newObjectName);
}); });
//Assertions against newly created object which define standard behavior
await expect(page.waitForURL(/.*&view=table/)).toBeTruthy();
await expect(page.locator('.l-browse-bar__object-name')).toContainText(newObjectName);
});
}); });
test.describe('Example Event Generator Telemetry Event Verficiation', () => { test.describe('Example Event Generator Telemetry Event Verficiation', () => {
test.fixme('telemetry is coming in for test event', async ({ page }) => {
test.fixme('telemetry is coming in for test event', async ({ page }) => {
// Go to object created in step one // Go to object created in step one
// Verify the telemetry table is filled with > 1 row // Verify the telemetry table is filled with > 1 row
}); });
test.fixme('telemetry is sorted by time ascending', async ({ page }) => { test.fixme('telemetry is sorted by time ascending', async ({ page }) => {
// Go to object created in step one // Go to object created in step one
// Verify the telemetry table has a class with "is-sorting asc" // Verify the telemetry table has a class with "is-sorting asc"
}); });
}); });

View File

@ -27,93 +27,113 @@ This test suite is dedicated to tests which verify the basic operations surround
const { test, expect } = require('../../../../baseFixtures'); const { test, expect } = require('../../../../baseFixtures');
test.describe('Sine Wave Generator', () => { test.describe('Sine Wave Generator', () => {
test('Create new Sine Wave Generator Object and validate create Form Logic', async ({ page, browserName }) => { test('Create new Sine Wave Generator Object and validate create Form Logic', async ({
// eslint-disable-next-line playwright/no-skipped-test page,
test.skip(browserName === 'firefox', 'This test needs to be updated to work with firefox'); browserName
}) => {
// eslint-disable-next-line playwright/no-skipped-test
test.skip(browserName === 'firefox', 'This test needs to be updated to work with firefox');
//Go to baseURL //Go to baseURL
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.click('button:has-text("Create")');
// Click Sine Wave Generator // Click Sine Wave Generator
await page.click('text=Sine Wave Generator'); await page.click('text=Sine Wave Generator');
// Verify that the each required field has required indicator // Verify that the each required field has required indicator
// Title // Title
await expect(page.locator('.c-form-row__state-indicator').first()).toHaveClass(/req/); await expect(page.locator('.c-form-row__state-indicator').first()).toHaveClass(/req/);
// Verify that the Notes row does not have a required indicator // Verify that the Notes row does not have a required indicator
await expect(page.locator('.c-form__section div:nth-child(3) .form-row .c-form-row__state-indicator')).not.toContain('.req'); await expect(
await page.locator('textarea[type="text"]').fill('Optional Note Text'); page.locator('.c-form__section div:nth-child(3) .form-row .c-form-row__state-indicator')
).not.toContain('.req');
await page.locator('textarea[type="text"]').fill('Optional Note Text');
// Period // Period
await expect(page.locator('div:nth-child(4) .c-form-row__state-indicator')).toHaveClass(/req/); await expect(page.locator('div:nth-child(4) .c-form-row__state-indicator')).toHaveClass(/req/);
// Amplitude // Amplitude
await expect(page.locator('div:nth-child(5) .c-form-row__state-indicator')).toHaveClass(/req/); await expect(page.locator('div:nth-child(5) .c-form-row__state-indicator')).toHaveClass(/req/);
// Offset // Offset
await expect(page.locator('div:nth-child(6) .c-form-row__state-indicator')).toHaveClass(/req/); await expect(page.locator('div:nth-child(6) .c-form-row__state-indicator')).toHaveClass(/req/);
// Data Rate // Data Rate
await expect(page.locator('div:nth-child(7) .c-form-row__state-indicator')).toHaveClass(/req/); await expect(page.locator('div:nth-child(7) .c-form-row__state-indicator')).toHaveClass(/req/);
// Phase // Phase
await expect(page.locator('div:nth-child(8) .c-form-row__state-indicator')).toHaveClass(/req/); await expect(page.locator('div:nth-child(8) .c-form-row__state-indicator')).toHaveClass(/req/);
// Randomness // Randomness
await expect(page.locator('div:nth-child(9) .c-form-row__state-indicator')).toHaveClass(/req/); await expect(page.locator('div:nth-child(9) .c-form-row__state-indicator')).toHaveClass(/req/);
// Verify that by removing value from required text field shows invalid indicator // Verify that by removing value from required text field shows invalid indicator
await page.locator('text=Properties Title Notes Period Amplitude Offset Data Rate (hz) Phase (radians) Ra >> input[type="text"]').fill(''); await page
await expect(page.locator('.c-form-row__state-indicator').first()).toHaveClass(/invalid/); .locator(
'text=Properties Title Notes Period Amplitude Offset Data Rate (hz) Phase (radians) Ra >> input[type="text"]'
)
.fill('');
await expect(page.locator('.c-form-row__state-indicator').first()).toHaveClass(/invalid/);
// Verify that by adding value to empty required text field changes invalid to valid indicator // Verify that by adding value to empty required text field changes invalid to valid indicator
await page.locator('text=Properties Title Notes Period Amplitude Offset Data Rate (hz) Phase (radians) Ra >> input[type="text"]').fill('New Sine Wave Generator'); await page
await expect(page.locator('.c-form-row__state-indicator').first()).toHaveClass(/valid/); .locator(
'text=Properties Title Notes Period Amplitude Offset Data Rate (hz) Phase (radians) Ra >> input[type="text"]'
)
.fill('New Sine Wave Generator');
await expect(page.locator('.c-form-row__state-indicator').first()).toHaveClass(/valid/);
// Verify that by removing value from required number field shows invalid indicator // Verify that by removing value from required number field shows invalid indicator
await page.locator('.field.control.l-input-sm input').first().fill(''); await page.locator('.field.control.l-input-sm input').first().fill('');
await expect(page.locator('div:nth-child(4) .c-form-row__state-indicator')).toHaveClass(/invalid/); await expect(page.locator('div:nth-child(4) .c-form-row__state-indicator')).toHaveClass(
/invalid/
);
// Verify that by adding value to empty required number field changes invalid to valid indicator // Verify that by adding value to empty required number field changes invalid to valid indicator
await page.locator('.field.control.l-input-sm input').first().fill('3'); await page.locator('.field.control.l-input-sm input').first().fill('3');
await expect(page.locator('div:nth-child(4) .c-form-row__state-indicator')).toHaveClass(/valid/); await expect(page.locator('div:nth-child(4) .c-form-row__state-indicator')).toHaveClass(
/valid/
);
// Verify that can change value of number field by up/down arrows keys // Verify that can change value of number field by up/down arrows keys
// Click .field.control.l-input-sm input >> nth=0 // Click .field.control.l-input-sm input >> nth=0
await page.locator('.field.control.l-input-sm input').first().click(); await page.locator('.field.control.l-input-sm input').first().click();
// Press ArrowUp 3 times to change value from 3 to 6 // Press ArrowUp 3 times to change value from 3 to 6
await page.locator('.field.control.l-input-sm input').first().press('ArrowUp'); await page.locator('.field.control.l-input-sm input').first().press('ArrowUp');
await page.locator('.field.control.l-input-sm input').first().press('ArrowUp'); await page.locator('.field.control.l-input-sm input').first().press('ArrowUp');
await page.locator('.field.control.l-input-sm input').first().press('ArrowUp'); await page.locator('.field.control.l-input-sm input').first().press('ArrowUp');
const value = await page.locator('.field.control.l-input-sm input').first().inputValue(); const value = await page.locator('.field.control.l-input-sm input').first().inputValue();
await expect(value).toBe('6'); await expect(value).toBe('6');
//Click text=OK //Click text=OK
await Promise.all([ await Promise.all([page.waitForNavigation(), page.click('button:has-text("OK")')]);
page.waitForNavigation(),
page.click('button:has-text("OK")')
]);
// Verify that the Sine Wave Generator is displayed and correct // Verify that the Sine Wave Generator is displayed and correct
// Verify object properties // Verify object properties
await expect(page.locator('.l-browse-bar__object-name')).toContainText('New Sine Wave Generator'); await expect(page.locator('.l-browse-bar__object-name')).toContainText(
'New Sine Wave Generator'
);
// Verify canvas rendered and can be interacted with // Verify canvas rendered and can be interacted with
await page.locator('canvas').nth(1).click({ await page
position: { .locator('canvas')
x: 341, .nth(1)
y: 28 .click({
} position: {
}); x: 341,
y: 28
}
});
// Verify that where we click on canvas shows the number we clicked on // Verify that where we click on canvas shows the number we clicked on
// Note that any number will do, we just care that a number exists // Note that any number will do, we just care that a number exists
await expect(page.locator('.value-to-display-nearestValue')).toContainText(/[+-]?([0-9]*[.])?[0-9]+/); await expect(page.locator('.value-to-display-nearestValue')).toContainText(
/[+-]?([0-9]*[.])?[0-9]+/
}); );
});
}); });

View File

@ -34,249 +34,265 @@ const jsonFilePath = 'e2e/test-data/ExampleLayouts.json';
const imageFilePath = 'e2e/test-data/rick.jpg'; const imageFilePath = 'e2e/test-data/rick.jpg';
test.describe('Form Validation Behavior', () => { test.describe('Form Validation Behavior', () => {
test('Required Field indicators appear if title is empty and can be corrected', async ({ page }) => { test('Required Field indicators appear if title is empty and can be corrected', async ({
//Go to baseURL page
await page.goto('./', { waitUntil: 'domcontentloaded' }); }) => {
//Go to baseURL
await page.goto('./', { waitUntil: 'domcontentloaded' });
await page.click('button:has-text("Create")'); await page.click('button:has-text("Create")');
await page.click(':nth-match(:text("Folder"), 2)'); await page.click(':nth-match(:text("Folder"), 2)');
// 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"]');
await page.fill('text=Properties Title Notes >> input[type="text"]', ''); await page.fill('text=Properties Title Notes >> input[type="text"]', '');
await page.press('text=Properties Title Notes >> input[type="text"]', 'Tab'); await page.press('text=Properties Title Notes >> input[type="text"]', 'Tab');
//Required Field Form Validation //Required Field Form Validation
await expect(page.locator('button:has-text("OK")')).toBeDisabled(); await expect(page.locator('button:has-text("OK")')).toBeDisabled();
await expect(page.locator('.c-form-row__state-indicator').first()).toHaveClass(/invalid/); await expect(page.locator('.c-form-row__state-indicator').first()).toHaveClass(/invalid/);
//Correct Form Validation for missing title and trigger validation with 'Tab' //Correct Form Validation for missing 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"]');
await page.fill('text=Properties Title Notes >> input[type="text"]', TEST_FOLDER); await page.fill('text=Properties Title Notes >> input[type="text"]', TEST_FOLDER);
await page.press('text=Properties Title Notes >> input[type="text"]', 'Tab'); await page.press('text=Properties Title Notes >> input[type="text"]', 'Tab');
//Required Field Form Validation is corrected //Required Field Form Validation is corrected
await expect(page.locator('button:has-text("OK")')).toBeEnabled(); await expect(page.locator('button:has-text("OK")')).toBeEnabled();
await expect(page.locator('.c-form-row__state-indicator').first()).not.toHaveClass(/invalid/); await expect(page.locator('.c-form-row__state-indicator').first()).not.toHaveClass(/invalid/);
//Finish Creating Domain Object //Finish Creating Domain Object
await Promise.all([ await Promise.all([page.waitForNavigation(), page.click('button:has-text("OK")')]);
page.waitForNavigation(),
page.click('button:has-text("OK")')
]);
//Verify that the Domain Object has been created with the corrected title property //Verify that the Domain Object has been created with the corrected title property
await expect(page.locator('.l-browse-bar__object-name')).toContainText(TEST_FOLDER); await expect(page.locator('.l-browse-bar__object-name')).toContainText(TEST_FOLDER);
}); });
}); });
test.describe('Form File Input Behavior', () => { test.describe('Form File Input Behavior', () => {
test.beforeEach(async ({ page }) => { test.beforeEach(async ({ page }) => {
await page.addInitScript({ path: path.join(__dirname, '../../helper', 'addInitFileInputObject.js') }); await page.addInitScript({
path: path.join(__dirname, '../../helper', 'addInitFileInputObject.js')
}); });
});
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);
await page.getByRole('button', { name: 'Save' }).click(); await page.getByRole('button', { name: 'Save' }).click();
const type = await page.locator('#file-input-type').textContent(); const type = await page.locator('#file-input-type').textContent();
await expect(type).toBe(`"string"`); await expect(type).toBe(`"string"`);
}); });
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);
await page.getByRole('button', { name: 'Save' }).click(); await page.getByRole('button', { name: 'Save' }).click();
const type = await page.locator('#file-input-type').textContent(); const type = await page.locator('#file-input-type').textContent();
await expect(type).toBe(`"object"`); await expect(type).toBe(`"object"`);
}); });
}); });
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({ path: path.join(__dirname, '../../helper', 'addNoneditableObject.js') }); await page.addInitScript({
path: path.join(__dirname, '../../helper', 'addNoneditableObject.js')
}); });
});
test('Persistability should be respected in the create form location field', async ({ page }) => { test('Persistability should be respected in the create form location field', async ({ page }) => {
test.info().annotations.push({ test.info().annotations.push({
type: 'issue', type: 'issue',
description: 'https://github.com/nasa/openmct/issues/4323' description: 'https://github.com/nasa/openmct/issues/4323'
});
await page.goto('./', { waitUntil: 'domcontentloaded' });
await page.click('button:has-text("Create")');
await page.click('text=Condition Set');
await page.locator('form[name="mctForm"] >> text=Persistence Testing').click();
const okButton = page.locator('button:has-text("OK")');
await expect(okButton).toBeDisabled();
}); });
await page.goto('./', { waitUntil: 'domcontentloaded' });
await page.click('button:has-text("Create")');
await page.click('text=Condition Set');
await page.locator('form[name="mctForm"] >> text=Persistence Testing').click();
const okButton = page.locator('button:has-text("OK")');
await expect(okButton).toBeDisabled();
});
}); });
test.describe('Persistence operations @couchdb', () => { test.describe('Persistence operations @couchdb', () => {
test.use({ failOnConsoleError: false }); test.use({ failOnConsoleError: false });
test('Editing object properties should generate a single persistence operation', async ({ page }) => { test('Editing object properties should generate a single persistence operation', async ({
test.info().annotations.push({ page
type: 'issue', }) => {
description: 'https://github.com/nasa/openmct/issues/5616' test.info().annotations.push({
}); type: 'issue',
description: 'https://github.com/nasa/openmct/issues/5616'
await page.goto('./', { waitUntil: 'domcontentloaded' });
// Create a new 'Clock' object with default settings
const clock = await createDomainObjectWithDefaults(page, {
type: 'Clock'
});
// Count all persistence operations (PUT requests) for this specific object
let putRequestCount = 0;
page.on('request', req => {
if (req.method() === 'PUT' && req.url().endsWith(clock.uuid)) {
putRequestCount += 1;
}
});
// Open the edit form for the clock object
await page.click('button[title="More options"]');
await page.click('li[title="Edit properties of this object."]');
// Modify the display format from default 12hr -> 24hr and click 'Save'
await page.locator('select[aria-label="12 or 24 hour clock"]').selectOption({ value: 'clock24' });
await page.click('button[aria-label="Save"]');
await expect.poll(() => putRequestCount, {
message: 'Verify a single PUT request was made to persist the object',
timeout: 1000
}).toEqual(1);
}); });
test('Can create an object after a conflict error @couchdb @2p', async ({ page, openmctConfig }) => {
test.info().annotations.push({
type: 'issue',
description: 'https://github.com/nasa/openmct/issues/5982'
});
const { myItemsFolderName } = openmctConfig;
// Instantiate a second page/tab
const page2 = await page.context().newPage();
// Both pages: Go to baseURL await page.goto('./', { waitUntil: 'domcontentloaded' });
await Promise.all([
page.goto('./', { waitUntil: 'networkidle' }),
page2.goto('./', { waitUntil: 'networkidle' })
]);
//Slow down the test a bit // Create a new 'Clock' object with default settings
await expect(page.getByRole('treeitem', { name: `  ${myItemsFolderName}` })).toBeVisible(); const clock = await createDomainObjectWithDefaults(page, {
await expect(page2.getByRole('treeitem', { name: `  ${myItemsFolderName}` })).toBeVisible(); type: 'Clock'
// Both pages: Click the Create button
await Promise.all([
page.click('button:has-text("Create")'),
page2.click('button:has-text("Create")')
]);
// Both pages: Click "Clock" in the Create menu
await Promise.all([
page.click(`li[role='menuitem']:text("Clock")`),
page2.click(`li[role='menuitem']:text("Clock")`)
]);
// Generate unique names for both objects
const nameInput = page.locator('form[name="mctForm"] .first input[type="text"]');
const nameInput2 = page2.locator('form[name="mctForm"] .first input[type="text"]');
// Both pages: Fill in the 'Name' form field.
await Promise.all([
nameInput.fill(""),
nameInput.fill(`Clock:${genUuid()}`),
nameInput2.fill(""),
nameInput2.fill(`Clock:${genUuid()}`)
]);
// Both pages: Fill the "Notes" section with information about the
// currently running test and its project.
const testNotes = page.testNotes;
const notesInput = page.locator('form[name="mctForm"] #notes-textarea');
const notesInput2 = page2.locator('form[name="mctForm"] #notes-textarea');
await Promise.all([
notesInput.fill(testNotes),
notesInput2.fill(testNotes)
]);
// Page 2: Click "OK" to create the domain object and wait for navigation.
// This will update the composition of the parent folder, setting the
// conditions for a conflict error from the first page.
await Promise.all([
page2.waitForLoadState(),
page2.click('[aria-label="Save"]'),
// Wait for Save Banner to appear
page2.waitForSelector('.c-message-banner__message')
]);
// Close Page 2, we're done with it.
await page2.close();
// Page 1: Click "OK" to create the domain object and wait for navigation.
// This will trigger a conflict error upon attempting to update
// the composition of the parent folder.
await Promise.all([
page.waitForLoadState(),
page.click('[aria-label="Save"]'),
// Wait for Save Banner to appear
page.waitForSelector('.c-message-banner__message')
]);
// Page 1: Verify that the conflict has occurred and an error notification is displayed.
await expect(page.locator('.c-message-banner__message', {
hasText: "Conflict detected while saving mine"
})).toBeVisible();
// Page 1: Start logging console errors from this point on
let errors = [];
page.on('console', (msg) => {
if (msg.type() === 'error') {
errors.push(msg.text());
}
});
// Page 1: Try to create a clock with the page that received the conflict.
const clockAfterConflict = await createDomainObjectWithDefaults(page, {
type: 'Clock'
});
// Page 1: Wait for save progress dialog to appear/disappear
await page.locator('.c-message-banner__message', {
hasText: 'Do not navigate away from this page or close this browser tab while this message is displayed.',
state: 'visible'
}).waitFor({ state: 'hidden' });
// Page 1: Navigate to 'My Items' and verify that the second clock was created
await page.goto('./#/browse/mine');
await expect(page.locator(`.c-grid-item__name[title="${clockAfterConflict.name}"]`)).toBeVisible();
// Verify no console errors occurred
expect(errors).toHaveLength(0);
}); });
// Count all persistence operations (PUT requests) for this specific object
let putRequestCount = 0;
page.on('request', (req) => {
if (req.method() === 'PUT' && req.url().endsWith(clock.uuid)) {
putRequestCount += 1;
}
});
// Open the edit form for the clock object
await page.click('button[title="More options"]');
await page.click('li[title="Edit properties of this object."]');
// Modify the display format from default 12hr -> 24hr and click 'Save'
await page
.locator('select[aria-label="12 or 24 hour clock"]')
.selectOption({ value: 'clock24' });
await page.click('button[aria-label="Save"]');
await expect
.poll(() => putRequestCount, {
message: 'Verify a single PUT request was made to persist the object',
timeout: 1000
})
.toEqual(1);
});
test('Can create an object after a conflict error @couchdb @2p', async ({
page,
openmctConfig
}) => {
test.info().annotations.push({
type: 'issue',
description: 'https://github.com/nasa/openmct/issues/5982'
});
const { myItemsFolderName } = openmctConfig;
// Instantiate a second page/tab
const page2 = await page.context().newPage();
// Both pages: Go to baseURL
await Promise.all([
page.goto('./', { waitUntil: 'networkidle' }),
page2.goto('./', { waitUntil: 'networkidle' })
]);
//Slow down the test a bit
await expect(page.getByRole('treeitem', { name: `  ${myItemsFolderName}` })).toBeVisible();
await expect(page2.getByRole('treeitem', { name: `  ${myItemsFolderName}` })).toBeVisible();
// Both pages: Click the Create button
await Promise.all([
page.click('button:has-text("Create")'),
page2.click('button:has-text("Create")')
]);
// Both pages: Click "Clock" in the Create menu
await Promise.all([
page.click(`li[role='menuitem']:text("Clock")`),
page2.click(`li[role='menuitem']:text("Clock")`)
]);
// Generate unique names for both objects
const nameInput = page.locator('form[name="mctForm"] .first input[type="text"]');
const nameInput2 = page2.locator('form[name="mctForm"] .first input[type="text"]');
// Both pages: Fill in the 'Name' form field.
await Promise.all([
nameInput.fill(''),
nameInput.fill(`Clock:${genUuid()}`),
nameInput2.fill(''),
nameInput2.fill(`Clock:${genUuid()}`)
]);
// Both pages: Fill the "Notes" section with information about the
// currently running test and its project.
const testNotes = page.testNotes;
const notesInput = page.locator('form[name="mctForm"] #notes-textarea');
const notesInput2 = page2.locator('form[name="mctForm"] #notes-textarea');
await Promise.all([notesInput.fill(testNotes), notesInput2.fill(testNotes)]);
// Page 2: Click "OK" to create the domain object and wait for navigation.
// This will update the composition of the parent folder, setting the
// conditions for a conflict error from the first page.
await Promise.all([
page2.waitForLoadState(),
page2.click('[aria-label="Save"]'),
// Wait for Save Banner to appear
page2.waitForSelector('.c-message-banner__message')
]);
// Close Page 2, we're done with it.
await page2.close();
// Page 1: Click "OK" to create the domain object and wait for navigation.
// This will trigger a conflict error upon attempting to update
// the composition of the parent folder.
await Promise.all([
page.waitForLoadState(),
page.click('[aria-label="Save"]'),
// Wait for Save Banner to appear
page.waitForSelector('.c-message-banner__message')
]);
// Page 1: Verify that the conflict has occurred and an error notification is displayed.
await expect(
page.locator('.c-message-banner__message', {
hasText: 'Conflict detected while saving mine'
})
).toBeVisible();
// Page 1: Start logging console errors from this point on
let errors = [];
page.on('console', (msg) => {
if (msg.type() === 'error') {
errors.push(msg.text());
}
});
// Page 1: Try to create a clock with the page that received the conflict.
const clockAfterConflict = await createDomainObjectWithDefaults(page, {
type: 'Clock'
});
// Page 1: Wait for save progress dialog to appear/disappear
await page
.locator('.c-message-banner__message', {
hasText:
'Do not navigate away from this page or close this browser tab while this message is displayed.',
state: 'visible'
})
.waitFor({ state: 'hidden' });
// Page 1: Navigate to 'My Items' and verify that the second clock was created
await page.goto('./#/browse/mine');
await expect(
page.locator(`.c-grid-item__name[title="${clockAfterConflict.name}"]`)
).toBeVisible();
// Verify no console errors occurred
expect(errors).toHaveLength(0);
});
}); });
test.describe('Form Correctness by Object Type', () => { test.describe('Form Correctness by Object Type', () => {
test.fixme('Verify correct behavior of number object (SWG)', async ({page}) => {}); test.fixme('Verify correct behavior of number object (SWG)', async ({ page }) => {});
test.fixme('Verify correct behavior of number object Timer', async ({page}) => {}); test.fixme('Verify correct behavior of number object Timer', async ({ page }) => {});
test.fixme('Verify correct behavior of number object Plan View', async ({page}) => {}); test.fixme('Verify correct behavior of number object Plan View', async ({ page }) => {});
test.fixme('Verify correct behavior of number object Clock', async ({page}) => {}); test.fixme('Verify correct behavior of number object Clock', async ({ page }) => {});
test.fixme('Verify correct behavior of number object Hyperlink', async ({page}) => {}); test.fixme('Verify correct behavior of number object Hyperlink', async ({ page }) => {});
}); });

View File

@ -29,21 +29,31 @@ const { test, expect } = require('../../baseFixtures.js');
const path = require('path'); const path = require('path');
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({ path: path.join(__dirname, '../../helper', 'addNoneditableObject.js') }); await page.addInitScript({
path: path.join(__dirname, '../../helper', 'addNoneditableObject.js')
});
});
test('Non-persistable objects should not show persistence related actions', async ({ page }) => {
await page.goto('./', { waitUntil: 'domcontentloaded' });
await page.locator('text=Persistence Testing').first().click({
button: 'right'
}); });
test('Non-persistable objects should not show persistence related actions', async ({ page }) => { const menuOptions = page.locator('.c-menu li');
await page.goto('./', { waitUntil: 'domcontentloaded' });
await page.locator('text=Persistence Testing').first().click({ await expect.soft(menuOptions).toContainText(['Open In New Tab', 'View', 'Create Link']);
button: 'right' await expect(menuOptions).not.toContainText([
}); 'Move',
'Duplicate',
const menuOptions = page.locator('.c-menu li'); 'Remove',
'Add New Folder',
await expect.soft(menuOptions).toContainText(['Open In New Tab', 'View', 'Create Link']); 'Edit Properties...',
await expect(menuOptions).not.toContainText(['Move', 'Duplicate', 'Remove', 'Add New Folder', 'Edit Properties...', 'Export as JSON', 'Import from JSON']); 'Export as JSON',
}); 'Import from JSON'
]);
});
}); });

View File

@ -28,249 +28,276 @@ const { test, expect } = require('../../pluginFixtures');
const { createDomainObjectWithDefaults } = require('../../appActions'); const { createDomainObjectWithDefaults } = require('../../appActions');
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 ({ page, openmctConfig }) => { test('Create a basic object and verify that it can be moved to another folder', async ({
const { myItemsFolderName } = openmctConfig; page,
openmctConfig
}) => {
const { myItemsFolderName } = openmctConfig;
// Go to Open MCT // Go to Open MCT
await page.goto('./'); await page.goto('./');
const parentFolder = await createDomainObjectWithDefaults(page, { const parentFolder = await createDomainObjectWithDefaults(page, {
type: 'Folder', type: 'Folder',
name: 'Parent Folder' name: 'Parent Folder'
});
const childFolder = await createDomainObjectWithDefaults(page, {
type: 'Folder',
name: 'Child Folder',
parent: parentFolder.uuid
});
const grandchildFolder = await createDomainObjectWithDefaults(page, {
type: 'Folder',
name: 'Grandchild Folder',
parent: childFolder.uuid
});
// Attempt to move parent to its own grandparent
await page.locator('button[title="Show selected item in tree"]').click();
const treePane = page.getByRole('tree', {
name: 'Main Tree'
});
await treePane.getByRole('treeitem', {
name: 'Parent Folder'
}).click({
button: 'right'
});
await page.getByRole('menuitem', {
name: /Move/
}).click();
const createModalTree = page.getByRole('tree', {
name: "Create Modal Tree"
});
const myItemsLocatorTreeItem = createModalTree.getByRole('treeitem', {
name: myItemsFolderName
});
await myItemsLocatorTreeItem.locator('.c-disclosure-triangle').click();
await myItemsLocatorTreeItem.click();
const parentFolderLocatorTreeItem = createModalTree.getByRole('treeitem', {
name: parentFolder.name
});
await parentFolderLocatorTreeItem.locator('.c-disclosure-triangle').click();
await parentFolderLocatorTreeItem.click();
await expect(page.locator('[aria-label="Save"]')).toBeDisabled();
const childFolderLocatorTreeItem = createModalTree.getByRole('treeitem', {
name: new RegExp(childFolder.name)
});
await childFolderLocatorTreeItem.locator('.c-disclosure-triangle').click();
await childFolderLocatorTreeItem.click();
await expect(page.locator('[aria-label="Save"]')).toBeDisabled();
const grandchildFolderLocatorTreeItem = createModalTree.getByRole('treeitem', {
name: grandchildFolder.name
});
await grandchildFolderLocatorTreeItem.locator('.c-disclosure-triangle').click();
await grandchildFolderLocatorTreeItem.click();
await expect(page.locator('[aria-label="Save"]')).toBeDisabled();
await parentFolderLocatorTreeItem.click();
await expect(page.locator('[aria-label="Save"]')).toBeDisabled();
await page.locator('[aria-label="Cancel"]').click();
// Move Child Folder from Parent Folder to My Items
await treePane.getByRole('treeitem', {
name: new RegExp(childFolder.name)
}).click({
button: 'right'
});
await page.getByRole('menuitem', {
name: /Move/
}).click();
await myItemsLocatorTreeItem.click();
await page.locator('[aria-label="Save"]').click();
const myItemsPaneTreeItem = treePane.getByRole('treeitem', {
name: myItemsFolderName
});
// Expect that Child Folder is in My Items, the root folder
expect(myItemsPaneTreeItem.locator('nth=0:has(text=Child Folder)')).toBeTruthy();
}); });
test('Create a basic object and verify that it cannot be moved to telemetry object without Composition Provider', async ({ page, openmctConfig }) => { const childFolder = await createDomainObjectWithDefaults(page, {
const { myItemsFolderName } = openmctConfig; type: 'Folder',
name: 'Child Folder',
// Go to Open MCT parent: parentFolder.uuid
await page.goto('./'); });
const grandchildFolder = await createDomainObjectWithDefaults(page, {
// Create Telemetry Table type: 'Folder',
let telemetryTable = 'Test Telemetry Table'; name: 'Grandchild Folder',
await page.locator('button:has-text("Create")').click(); parent: childFolder.uuid
await page.locator('li[role="menuitem"]:has-text("Telemetry Table")').click();
await page.locator('text=Properties Title Notes >> input[type="text"]').click();
await page.locator('text=Properties Title Notes >> input[type="text"]').fill(telemetryTable);
await page.locator('button:has-text("OK")').click();
// Finish editing and save Telemetry Table
await page.locator('.c-button--menu.c-button--major.icon-save').click();
await page.locator('text=Save and Finish Editing').click();
// Create New Folder Basic Domain Object
let folder = 'Test Folder';
await page.locator('button:has-text("Create")').click();
await page.locator('li[role="menuitem"]:has-text("Folder")').click();
await page.locator('text=Properties Title Notes >> input[type="text"]').click();
await page.locator('text=Properties Title Notes >> input[type="text"]').fill(folder);
// See if it's possible to put the folder in the Telemetry object during creation (Soft Assert)
await page.locator(`form[name="mctForm"] >> text=${telemetryTable}`).click();
let okButton = page.locator('button.c-button.c-button--major:has-text("OK")');
let okButtonStateDisabled = await okButton.isDisabled();
expect.soft(okButtonStateDisabled).toBeTruthy();
// Continue test regardless of assertion and create it in My Items
await page.locator(`form[name="mctForm"] >> text=${myItemsFolderName}`).click();
await page.locator('button:has-text("OK")').click();
// Open My Items
await page.locator(`text=Open MCT ${myItemsFolderName} >> span`).nth(3).click();
// Select Folder Object and select Move from context menu
await Promise.all([
page.waitForNavigation(),
page.locator(`a:has-text("${folder}")`).click()
]);
await page.locator('.c-tree__item.is-navigated-object .c-tree__item__label .c-tree__item__type-icon').click({
button: 'right'
});
await page.locator('li.icon-move').click();
// See if it's possible to put the folder in the Telemetry object after creation
await page.locator(`text=Location Open MCT ${myItemsFolderName} >> span`).nth(3).click();
await page.locator(`form[name="mctForm"] >> text=${telemetryTable}`).click();
let okButton2 = page.locator('button.c-button.c-button--major:has-text("OK")');
let okButtonStateDisabled2 = await okButton2.isDisabled();
expect(okButtonStateDisabled2).toBeTruthy();
}); });
test('Create a basic object and verify that it can be linked to another folder', async ({ page, openmctConfig }) => { // Attempt to move parent to its own grandparent
const { myItemsFolderName } = openmctConfig; await page.locator('button[title="Show selected item in tree"]').click();
// Go to Open MCT const treePane = page.getByRole('tree', {
await page.goto('./'); name: 'Main Tree'
const parentFolder = await createDomainObjectWithDefaults(page, {
type: 'Folder',
name: 'Parent Folder'
});
const childFolder = await createDomainObjectWithDefaults(page, {
type: 'Folder',
name: 'Child Folder',
parent: parentFolder.uuid
});
const grandchildFolder = await createDomainObjectWithDefaults(page, {
type: 'Folder',
name: 'Grandchild Folder',
parent: childFolder.uuid
});
// Attempt to move parent to its own grandparent
await page.locator('button[title="Show selected item in tree"]').click();
const treePane = page.getByRole('tree', {
name: 'Main Tree'
});
await treePane.getByRole('treeitem', {
name: 'Parent Folder'
}).click({
button: 'right'
});
await page.getByRole('menuitem', {
name: /Move/
}).click();
const createModalTree = page.getByRole('tree', {
name: "Create Modal Tree"
});
const myItemsLocatorTreeItem = createModalTree.getByRole('treeitem', {
name: myItemsFolderName
});
await myItemsLocatorTreeItem.locator('.c-disclosure-triangle').click();
await myItemsLocatorTreeItem.click();
const parentFolderLocatorTreeItem = createModalTree.getByRole('treeitem', {
name: parentFolder.name
});
await parentFolderLocatorTreeItem.locator('.c-disclosure-triangle').click();
await parentFolderLocatorTreeItem.click();
await expect(page.locator('[aria-label="Save"]')).toBeDisabled();
const childFolderLocatorTreeItem = createModalTree.getByRole('treeitem', {
name: new RegExp(childFolder.name)
});
await childFolderLocatorTreeItem.locator('.c-disclosure-triangle').click();
await childFolderLocatorTreeItem.click();
await expect(page.locator('[aria-label="Save"]')).toBeDisabled();
const grandchildFolderLocatorTreeItem = createModalTree.getByRole('treeitem', {
name: grandchildFolder.name
});
await grandchildFolderLocatorTreeItem.locator('.c-disclosure-triangle').click();
await grandchildFolderLocatorTreeItem.click();
await expect(page.locator('[aria-label="Save"]')).toBeDisabled();
await parentFolderLocatorTreeItem.click();
await expect(page.locator('[aria-label="Save"]')).toBeDisabled();
await page.locator('[aria-label="Cancel"]').click();
// Move Child Folder from Parent Folder to My Items
await treePane.getByRole('treeitem', {
name: new RegExp(childFolder.name)
}).click({
button: 'right'
});
await page.getByRole('menuitem', {
name: /Link/
}).click();
await myItemsLocatorTreeItem.click();
await page.locator('[aria-label="Save"]').click();
const myItemsPaneTreeItem = treePane.getByRole('treeitem', {
name: myItemsFolderName
});
// Expect that Child Folder is in My Items, the root folder
expect(myItemsPaneTreeItem.locator('nth=0:has(text=Child Folder)')).toBeTruthy();
}); });
await treePane
.getByRole('treeitem', {
name: 'Parent Folder'
})
.click({
button: 'right'
});
await page
.getByRole('menuitem', {
name: /Move/
})
.click();
const createModalTree = page.getByRole('tree', {
name: 'Create Modal Tree'
});
const myItemsLocatorTreeItem = createModalTree.getByRole('treeitem', {
name: myItemsFolderName
});
await myItemsLocatorTreeItem.locator('.c-disclosure-triangle').click();
await myItemsLocatorTreeItem.click();
const parentFolderLocatorTreeItem = createModalTree.getByRole('treeitem', {
name: parentFolder.name
});
await parentFolderLocatorTreeItem.locator('.c-disclosure-triangle').click();
await parentFolderLocatorTreeItem.click();
await expect(page.locator('[aria-label="Save"]')).toBeDisabled();
const childFolderLocatorTreeItem = createModalTree.getByRole('treeitem', {
name: new RegExp(childFolder.name)
});
await childFolderLocatorTreeItem.locator('.c-disclosure-triangle').click();
await childFolderLocatorTreeItem.click();
await expect(page.locator('[aria-label="Save"]')).toBeDisabled();
const grandchildFolderLocatorTreeItem = createModalTree.getByRole('treeitem', {
name: grandchildFolder.name
});
await grandchildFolderLocatorTreeItem.locator('.c-disclosure-triangle').click();
await grandchildFolderLocatorTreeItem.click();
await expect(page.locator('[aria-label="Save"]')).toBeDisabled();
await parentFolderLocatorTreeItem.click();
await expect(page.locator('[aria-label="Save"]')).toBeDisabled();
await page.locator('[aria-label="Cancel"]').click();
// Move Child Folder from Parent Folder to My Items
await treePane
.getByRole('treeitem', {
name: new RegExp(childFolder.name)
})
.click({
button: 'right'
});
await page
.getByRole('menuitem', {
name: /Move/
})
.click();
await myItemsLocatorTreeItem.click();
await page.locator('[aria-label="Save"]').click();
const myItemsPaneTreeItem = treePane.getByRole('treeitem', {
name: myItemsFolderName
});
// Expect that Child Folder is in My Items, the root folder
expect(myItemsPaneTreeItem.locator('nth=0:has(text=Child Folder)')).toBeTruthy();
});
test('Create a basic object and verify that it cannot be moved to telemetry object without Composition Provider', async ({
page,
openmctConfig
}) => {
const { myItemsFolderName } = openmctConfig;
// Go to Open MCT
await page.goto('./');
// Create Telemetry Table
let telemetryTable = 'Test Telemetry Table';
await page.locator('button:has-text("Create")').click();
await page.locator('li[role="menuitem"]:has-text("Telemetry Table")').click();
await page.locator('text=Properties Title Notes >> input[type="text"]').click();
await page.locator('text=Properties Title Notes >> input[type="text"]').fill(telemetryTable);
await page.locator('button:has-text("OK")').click();
// Finish editing and save Telemetry Table
await page.locator('.c-button--menu.c-button--major.icon-save').click();
await page.locator('text=Save and Finish Editing').click();
// Create New Folder Basic Domain Object
let folder = 'Test Folder';
await page.locator('button:has-text("Create")').click();
await page.locator('li[role="menuitem"]:has-text("Folder")').click();
await page.locator('text=Properties Title Notes >> input[type="text"]').click();
await page.locator('text=Properties Title Notes >> input[type="text"]').fill(folder);
// See if it's possible to put the folder in the Telemetry object during creation (Soft Assert)
await page.locator(`form[name="mctForm"] >> text=${telemetryTable}`).click();
let okButton = page.locator('button.c-button.c-button--major:has-text("OK")');
let okButtonStateDisabled = await okButton.isDisabled();
expect.soft(okButtonStateDisabled).toBeTruthy();
// Continue test regardless of assertion and create it in My Items
await page.locator(`form[name="mctForm"] >> text=${myItemsFolderName}`).click();
await page.locator('button:has-text("OK")').click();
// Open My Items
await page.locator(`text=Open MCT ${myItemsFolderName} >> span`).nth(3).click();
// Select Folder Object and select Move from context menu
await Promise.all([page.waitForNavigation(), page.locator(`a:has-text("${folder}")`).click()]);
await page
.locator('.c-tree__item.is-navigated-object .c-tree__item__label .c-tree__item__type-icon')
.click({
button: 'right'
});
await page.locator('li.icon-move').click();
// See if it's possible to put the folder in the Telemetry object after creation
await page.locator(`text=Location Open MCT ${myItemsFolderName} >> span`).nth(3).click();
await page.locator(`form[name="mctForm"] >> text=${telemetryTable}`).click();
let okButton2 = page.locator('button.c-button.c-button--major:has-text("OK")');
let okButtonStateDisabled2 = await okButton2.isDisabled();
expect(okButtonStateDisabled2).toBeTruthy();
});
test('Create a basic object and verify that it can be linked to another folder', async ({
page,
openmctConfig
}) => {
const { myItemsFolderName } = openmctConfig;
// Go to Open MCT
await page.goto('./');
const parentFolder = await createDomainObjectWithDefaults(page, {
type: 'Folder',
name: 'Parent Folder'
});
const childFolder = await createDomainObjectWithDefaults(page, {
type: 'Folder',
name: 'Child Folder',
parent: parentFolder.uuid
});
const grandchildFolder = await createDomainObjectWithDefaults(page, {
type: 'Folder',
name: 'Grandchild Folder',
parent: childFolder.uuid
});
// Attempt to move parent to its own grandparent
await page.locator('button[title="Show selected item in tree"]').click();
const treePane = page.getByRole('tree', {
name: 'Main Tree'
});
await treePane
.getByRole('treeitem', {
name: 'Parent Folder'
})
.click({
button: 'right'
});
await page
.getByRole('menuitem', {
name: /Move/
})
.click();
const createModalTree = page.getByRole('tree', {
name: 'Create Modal Tree'
});
const myItemsLocatorTreeItem = createModalTree.getByRole('treeitem', {
name: myItemsFolderName
});
await myItemsLocatorTreeItem.locator('.c-disclosure-triangle').click();
await myItemsLocatorTreeItem.click();
const parentFolderLocatorTreeItem = createModalTree.getByRole('treeitem', {
name: parentFolder.name
});
await parentFolderLocatorTreeItem.locator('.c-disclosure-triangle').click();
await parentFolderLocatorTreeItem.click();
await expect(page.locator('[aria-label="Save"]')).toBeDisabled();
const childFolderLocatorTreeItem = createModalTree.getByRole('treeitem', {
name: new RegExp(childFolder.name)
});
await childFolderLocatorTreeItem.locator('.c-disclosure-triangle').click();
await childFolderLocatorTreeItem.click();
await expect(page.locator('[aria-label="Save"]')).toBeDisabled();
const grandchildFolderLocatorTreeItem = createModalTree.getByRole('treeitem', {
name: grandchildFolder.name
});
await grandchildFolderLocatorTreeItem.locator('.c-disclosure-triangle').click();
await grandchildFolderLocatorTreeItem.click();
await expect(page.locator('[aria-label="Save"]')).toBeDisabled();
await parentFolderLocatorTreeItem.click();
await expect(page.locator('[aria-label="Save"]')).toBeDisabled();
await page.locator('[aria-label="Cancel"]').click();
// Move Child Folder from Parent Folder to My Items
await treePane
.getByRole('treeitem', {
name: new RegExp(childFolder.name)
})
.click({
button: 'right'
});
await page
.getByRole('menuitem', {
name: /Link/
})
.click();
await myItemsLocatorTreeItem.click();
await page.locator('[aria-label="Save"]').click();
const myItemsPaneTreeItem = treePane.getByRole('treeitem', {
name: myItemsFolderName
});
// Expect that Child Folder is in My Items, the root folder
expect(myItemsPaneTreeItem.locator('nth=0:has(text=Child Folder)')).toBeTruthy();
});
}); });
test.fixme('Cannot move a previously created domain object to non-peristable object in Move Modal', async ({ page }) => { test.fixme(
'Cannot move a previously created domain object to non-peristable object in Move Modal',
async ({ page }) => {
//Create a domain object //Create a domain object
//Save Domain object //Save Domain object
//Move Object and verify that cannot select non-persistable object //Move Object and verify that cannot select non-persistable object
//Move Object to My Items //Move Object to My Items
//Verify successful move //Verify successful move
}); }
);

View File

@ -28,85 +28,91 @@ const { createDomainObjectWithDefaults, createNotification } = require('../../ap
const { test, expect } = require('../../pluginFixtures'); const { test, expect } = require('../../pluginFixtures');
test.describe('Notifications List', () => { test.describe('Notifications List', () => {
test('Notifications can be dismissed individually', async ({ page }) => { test('Notifications can be dismissed individually', async ({ page }) => {
test.info().annotations.push({ test.info().annotations.push({
type: 'issue', type: 'issue',
description: 'https://github.com/nasa/openmct/issues/6122' description: 'https://github.com/nasa/openmct/issues/6122'
});
// Go to baseURL
await page.goto('./', { waitUntil: 'domcontentloaded' });
// Create an error notification with the message "Error message"
await createNotification(page, {
severity: 'error',
message: 'Error message'
});
// Create an alert notification with the message "Alert message"
await createNotification(page, {
severity: 'alert',
message: 'Alert message'
});
// Verify that there is a button with aria-label "Review 2 Notifications"
expect(await page.locator('button[aria-label="Review 2 Notifications"]').count()).toBe(1);
// Click on button with aria-label "Review 2 Notifications"
await page.click('button[aria-label="Review 2 Notifications"]');
// Click on button with aria-label="Dismiss notification of Error message"
await page.click('button[aria-label="Dismiss notification of Error message"]');
// Verify there is no a notification (listitem) with the text "Error message" since it was dismissed
expect(await page.locator('div[role="dialog"] div[role="listitem"]').innerText()).not.toContain('Error message');
// Verify there is still a notification (listitem) with the text "Alert message"
expect(await page.locator('div[role="dialog"] div[role="listitem"]').innerText()).toContain('Alert message');
// Click on button with aria-label="Dismiss notification of Alert message"
await page.click('button[aria-label="Dismiss notification of Alert message"]');
// Verify that there is no dialog since the notification overlay was closed automatically after all notifications were dismissed
expect(await page.locator('div[role="dialog"]').count()).toBe(0);
}); });
// Go to baseURL
await page.goto('./', { waitUntil: 'domcontentloaded' });
// Create an error notification with the message "Error message"
await createNotification(page, {
severity: 'error',
message: 'Error message'
});
// Create an alert notification with the message "Alert message"
await createNotification(page, {
severity: 'alert',
message: 'Alert message'
});
// Verify that there is a button with aria-label "Review 2 Notifications"
expect(await page.locator('button[aria-label="Review 2 Notifications"]').count()).toBe(1);
// Click on button with aria-label "Review 2 Notifications"
await page.click('button[aria-label="Review 2 Notifications"]');
// Click on button with aria-label="Dismiss notification of Error message"
await page.click('button[aria-label="Dismiss notification of Error message"]');
// Verify there is no a notification (listitem) with the text "Error message" since it was dismissed
expect(await page.locator('div[role="dialog"] div[role="listitem"]').innerText()).not.toContain(
'Error message'
);
// Verify there is still a notification (listitem) with the text "Alert message"
expect(await page.locator('div[role="dialog"] div[role="listitem"]').innerText()).toContain(
'Alert message'
);
// Click on button with aria-label="Dismiss notification of Alert message"
await page.click('button[aria-label="Dismiss notification of Alert message"]');
// Verify that there is no dialog since the notification overlay was closed automatically after all notifications were dismissed
expect(await page.locator('div[role="dialog"]').count()).toBe(0);
});
}); });
test.describe('Notification Overlay', () => { test.describe('Notification Overlay', () => {
test('Closing notification list after notification banner disappeared does not cause it to open automatically', async ({ page }) => { test('Closing notification list after notification banner disappeared does not cause it to open automatically', async ({
test.info().annotations.push({ page
type: 'issue', }) => {
description: 'https://github.com/nasa/openmct/issues/6130' test.info().annotations.push({
}); type: 'issue',
description: 'https://github.com/nasa/openmct/issues/6130'
// Go to baseURL
await page.goto('./', { waitUntil: 'domcontentloaded' });
// Create a new Display Layout object
await createDomainObjectWithDefaults(page, { type: 'Display Layout' });
// Click on the button "Review 1 Notification"
await page.click('button[aria-label="Review 1 Notification"]');
// Verify that Notification List is open
expect(await page.locator('div[role="dialog"]').isVisible()).toBe(true);
// Wait until there is no Notification Banner
await page.waitForSelector('div[role="alert"]', { state: 'detached'});
// Click on the "Close" button of the Notification List
await page.click('button[aria-label="Close"]');
// On the Display Layout object, click on the "Edit" button
await page.click('button[title="Edit"]');
// Click on the "Save" button
await page.click('button[title="Save"]');
// Click on the "Save and Finish Editing" option
await page.click('li[title="Save and Finish Editing"]');
// Verify that Notification List is NOT open
expect(await page.locator('div[role="dialog"]').isVisible()).toBe(false);
}); });
// Go to baseURL
await page.goto('./', { waitUntil: 'domcontentloaded' });
// Create a new Display Layout object
await createDomainObjectWithDefaults(page, { type: 'Display Layout' });
// Click on the button "Review 1 Notification"
await page.click('button[aria-label="Review 1 Notification"]');
// Verify that Notification List is open
expect(await page.locator('div[role="dialog"]').isVisible()).toBe(true);
// Wait until there is no Notification Banner
await page.waitForSelector('div[role="alert"]', { state: 'detached' });
// Click on the "Close" button of the Notification List
await page.click('button[aria-label="Close"]');
// On the Display Layout object, click on the "Edit" button
await page.click('button[title="Edit"]');
// Click on the "Save" button
await page.click('button[title="Save"]');
// Click on the "Save and Finish Editing" option
await page.click('li[title="Save and Finish Editing"]');
// Verify that Notification List is NOT open
expect(await page.locator('div[role="dialog"]').isVisible()).toBe(false);
});
}); });

View File

@ -20,84 +20,108 @@
* at runtime from the About dialog for additional information. * at runtime from the About dialog for additional information.
*****************************************************************************/ *****************************************************************************/
const { test, expect } = require('../../../pluginFixtures'); const { test, expect } = require('../../../pluginFixtures');
const { createPlanFromJSON, createDomainObjectWithDefaults, selectInspectorTab } = require('../../../appActions'); const {
createPlanFromJSON,
createDomainObjectWithDefaults,
selectInspectorTab
} = require('../../../appActions');
const testPlan1 = require('../../../test-data/examplePlans/ExamplePlan_Small1.json'); const testPlan1 = require('../../../test-data/examplePlans/ExamplePlan_Small1.json');
const testPlan2 = require('../../../test-data/examplePlans/ExamplePlan_Small2.json'); const testPlan2 = require('../../../test-data/examplePlans/ExamplePlan_Small2.json');
const { assertPlanActivities, setBoundsToSpanAllActivities } = require('../../../helper/planningUtils'); const {
assertPlanActivities,
setBoundsToSpanAllActivities
} = require('../../../helper/planningUtils');
const { getPreciseDuration } = require('../../../../src/utils/duration'); const { getPreciseDuration } = require('../../../../src/utils/duration');
test.describe("Gantt Chart", () => { test.describe('Gantt Chart', () => {
let ganttChart; let ganttChart;
let plan; let plan;
test.beforeEach(async ({ page }) => { test.beforeEach(async ({ page }) => {
await page.goto('./', { waitUntil: 'domcontentloaded' }); await page.goto('./', { waitUntil: 'domcontentloaded' });
ganttChart = await createDomainObjectWithDefaults(page, { ganttChart = await createDomainObjectWithDefaults(page, {
type: 'Gantt Chart' type: 'Gantt Chart'
}); });
plan = await createPlanFromJSON(page, { plan = await createPlanFromJSON(page, {
json: testPlan1, json: testPlan1,
parent: ganttChart.uuid parent: ganttChart.uuid
}); });
});
test('Displays all plan events', async ({ page }) => {
await page.goto(ganttChart.url);
await assertPlanActivities(page, testPlan1, ganttChart.url);
});
test('Replaces a plan with a new plan', async ({ page }) => {
await assertPlanActivities(page, testPlan1, ganttChart.url);
await createPlanFromJSON(page, {
json: testPlan2,
parent: ganttChart.uuid
});
const replaceModal = page
.getByRole('dialog')
.filter({ hasText: 'This action will replace the current Plan. Do you want to continue?' });
await expect(replaceModal).toBeVisible();
await page.getByRole('button', { name: 'OK' }).click();
await assertPlanActivities(page, testPlan2, ganttChart.url);
});
test('Can select a single activity and display its details in the inspector', async ({
page
}) => {
test.slow();
await page.goto(ganttChart.url);
await setBoundsToSpanAllActivities(page, testPlan1, ganttChart.url);
const activities = Object.values(testPlan1).flat();
const activity = activities[0];
await page
.locator('g')
.filter({ hasText: new RegExp(activity.name) })
.click();
await selectInspectorTab(page, 'Activity');
const startDateTime = await page
.locator(
'.c-inspect-properties__label:has-text("Start DateTime")+.c-inspect-properties__value'
)
.innerText();
const endDateTime = await page
.locator('.c-inspect-properties__label:has-text("End DateTime")+.c-inspect-properties__value')
.innerText();
const duration = await page
.locator('.c-inspect-properties__label:has-text("duration")+.c-inspect-properties__value')
.innerText();
const expectedStartDate = new Date(activity.start).toISOString();
const actualStartDate = new Date(startDateTime).toISOString();
const expectedEndDate = new Date(activity.end).toISOString();
const actualEndDate = new Date(endDateTime).toISOString();
const expectedDuration = getPreciseDuration(activity.end - activity.start);
const actualDuration = duration;
expect(expectedStartDate).toEqual(actualStartDate);
expect(expectedEndDate).toEqual(actualEndDate);
expect(expectedDuration).toEqual(actualDuration);
});
test("Displays a Plan's draft status", async ({ page }) => {
test.info().annotations.push({
type: 'issue',
description: 'https://github.com/nasa/openmct/issues/6641'
}); });
test("Displays all plan events", async ({ page }) => { // Mark the Plan's status as draft in the OpenMCT API
await page.goto(ganttChart.url); await page.evaluate(async (planObject) => {
await window.openmct.status.set(planObject.uuid, 'draft');
}, plan);
await assertPlanActivities(page, testPlan1, ganttChart.url); // Navigate to the Gantt Chart
}); await page.goto(ganttChart.url);
test("Replaces a plan with a new plan", async ({ page }) => {
await assertPlanActivities(page, testPlan1, ganttChart.url);
await createPlanFromJSON(page, {
json: testPlan2,
parent: ganttChart.uuid
});
const replaceModal = page.getByRole('dialog').filter({ hasText: "This action will replace the current Plan. Do you want to continue?" });
await expect(replaceModal).toBeVisible();
await page.getByRole('button', { name: 'OK' }).click();
await assertPlanActivities(page, testPlan2, ganttChart.url); // Assert that the Plan's status is displayed as draft
}); expect(await page.locator('.u-contents.c-swimlane.is-status--draft').count()).toBe(
test("Can select a single activity and display its details in the inspector", async ({ page }) => { Object.keys(testPlan1).length
test.slow(); );
await page.goto(ganttChart.url); });
await setBoundsToSpanAllActivities(page, testPlan1, ganttChart.url);
const activities = Object.values(testPlan1).flat();
const activity = activities[0];
await page.locator('g').filter({ hasText: new RegExp(activity.name) }).click();
await selectInspectorTab(page, 'Activity');
const startDateTime = await page.locator('.c-inspect-properties__label:has-text("Start DateTime")+.c-inspect-properties__value').innerText();
const endDateTime = await page.locator('.c-inspect-properties__label:has-text("End DateTime")+.c-inspect-properties__value').innerText();
const duration = await page.locator('.c-inspect-properties__label:has-text("duration")+.c-inspect-properties__value').innerText();
const expectedStartDate = new Date(activity.start).toISOString();
const actualStartDate = new Date(startDateTime).toISOString();
const expectedEndDate = new Date(activity.end).toISOString();
const actualEndDate = new Date(endDateTime).toISOString();
const expectedDuration = getPreciseDuration(activity.end - activity.start);
const actualDuration = duration;
expect(expectedStartDate).toEqual(actualStartDate);
expect(expectedEndDate).toEqual(actualEndDate);
expect(expectedDuration).toEqual(actualDuration);
});
test("Displays a Plan's draft status", async ({ page }) => {
test.info().annotations.push({
type: 'issue',
description: 'https://github.com/nasa/openmct/issues/6641'
});
// Mark the Plan's status as draft in the OpenMCT API
await page.evaluate(async (planObject) => {
await window.openmct.status.set(planObject.uuid, 'draft');
}, plan);
// Navigate to the Gantt Chart
await page.goto(ganttChart.url);
// Assert that the Plan's status is displayed as draft
expect(await page.locator('.u-contents.c-swimlane.is-status--draft').count()).toBe(Object.keys(testPlan1).length);
});
}); });

View File

@ -24,16 +24,16 @@ const { createPlanFromJSON } = require('../../../appActions');
const testPlan1 = require('../../../test-data/examplePlans/ExamplePlan_Small1.json'); const testPlan1 = require('../../../test-data/examplePlans/ExamplePlan_Small1.json');
const { assertPlanActivities } = require('../../../helper/planningUtils'); const { assertPlanActivities } = require('../../../helper/planningUtils');
test.describe("Plan", () => { test.describe('Plan', () => {
let plan; let plan;
test.beforeEach(async ({ page }) => { test.beforeEach(async ({ page }) => {
await page.goto('./', { waitUntil: 'domcontentloaded' }); await page.goto('./', { waitUntil: 'domcontentloaded' });
plan = await createPlanFromJSON(page, { plan = await createPlanFromJSON(page, {
json: testPlan1 json: testPlan1
});
}); });
});
test("Displays all plan events", async ({ page }) => { test('Displays all plan events', async ({ page }) => {
await assertPlanActivities(page, testPlan1, plan.url); await assertPlanActivities(page, testPlan1, plan.url);
}); });
}); });

View File

@ -24,158 +24,164 @@ const { test, expect } = require('../../../pluginFixtures');
const { createDomainObjectWithDefaults, createPlanFromJSON } = require('../../../appActions'); const { createDomainObjectWithDefaults, createPlanFromJSON } = require('../../../appActions');
const testPlan = { const testPlan = {
"TEST_GROUP": [ TEST_GROUP: [
{ {
"name": "Past event 1", name: 'Past event 1',
"start": 1660320408000, start: 1660320408000,
"end": 1660343797000, end: 1660343797000,
"type": "TEST-GROUP", type: 'TEST-GROUP',
"color": "orange", color: 'orange',
"textColor": "white" textColor: 'white'
}, },
{ {
"name": "Past event 2", name: 'Past event 2',
"start": 1660406808000, start: 1660406808000,
"end": 1660429160000, end: 1660429160000,
"type": "TEST-GROUP", type: 'TEST-GROUP',
"color": "orange", color: 'orange',
"textColor": "white" textColor: 'white'
}, },
{ {
"name": "Past event 3", name: 'Past event 3',
"start": 1660493208000, start: 1660493208000,
"end": 1660503981000, end: 1660503981000,
"type": "TEST-GROUP", type: 'TEST-GROUP',
"color": "orange", color: 'orange',
"textColor": "white" textColor: 'white'
}, },
{ {
"name": "Past event 4", name: 'Past event 4',
"start": 1660579608000, start: 1660579608000,
"end": 1660624108000, end: 1660624108000,
"type": "TEST-GROUP", type: 'TEST-GROUP',
"color": "orange", color: 'orange',
"textColor": "white" textColor: 'white'
}, },
{ {
"name": "Past event 5", name: 'Past event 5',
"start": 1660666008000, start: 1660666008000,
"end": 1660681529000, end: 1660681529000,
"type": "TEST-GROUP", type: 'TEST-GROUP',
"color": "orange", color: 'orange',
"textColor": "white" textColor: 'white'
} }
] ]
}; };
test.describe("Time Strip", () => { test.describe('Time Strip', () => {
test("Create two Time Strips, add a single Plan to both, and verify they can have separate Indepdenent Time Contexts @unstable", async ({ page }) => { test('Create two Time Strips, add a single Plan to both, and verify they can have separate Indepdenent Time Contexts @unstable', async ({
test.info().annotations.push({ page
type: 'issue', }) => {
description: 'https://github.com/nasa/openmct/issues/5627' test.info().annotations.push({
}); type: 'issue',
description: 'https://github.com/nasa/openmct/issues/5627'
// Constant locators
const independentTimeConductorInputs = page.locator('.l-shell__main-independent-time-conductor .c-input--datetime');
const activityBounds = page.locator('.activity-bounds');
// Goto baseURL
await page.goto('./', { waitUntil: 'domcontentloaded' });
const timestrip = await test.step("Create a Time Strip", async () => {
const createdTimeStrip = await createDomainObjectWithDefaults(page, { type: 'Time Strip' });
const objectName = await page.locator('.l-browse-bar__object-name').innerText();
expect(objectName).toBe(createdTimeStrip.name);
return createdTimeStrip;
});
const plan = await test.step("Create a Plan and add it to the timestrip", async () => {
const createdPlan = await createPlanFromJSON(page, {
name: 'Test Plan',
json: testPlan
});
await page.goto(timestrip.url);
// Expand the tree to show the plan
await page.click("button[title='Show selected item in tree']");
await page.dragAndDrop(`role=treeitem[name=/${createdPlan.name}/]`, '.c-object-view');
await page.click("button[title='Save']");
await page.click("li[title='Save and Finish Editing']");
const startBound = testPlan.TEST_GROUP[0].start;
const endBound = testPlan.TEST_GROUP[testPlan.TEST_GROUP.length - 1].end;
// Switch to fixed time mode with all plan events within the bounds
await page.goto(`${timestrip.url}?tc.mode=fixed&tc.startBound=${startBound}&tc.endBound=${endBound}&tc.timeSystem=utc&view=time-strip.view`);
// Verify all events are displayed
const eventCount = await page.locator('.activity-bounds').count();
expect(eventCount).toEqual(testPlan.TEST_GROUP.length);
return createdPlan;
});
await test.step("TimeStrip can use the Independent Time Conductor", async () => {
// Activate Independent Time Conductor in Fixed Time Mode
await page.click('.c-toggle-switch__slider');
expect(await activityBounds.count()).toEqual(0);
// Set the independent time bounds so that only one event is shown
const startBound = testPlan.TEST_GROUP[0].start;
const endBound = testPlan.TEST_GROUP[0].end;
const startBoundString = new Date(startBound).toISOString().replace('T', ' ');
const endBoundString = new Date(endBound).toISOString().replace('T', ' ');
await independentTimeConductorInputs.nth(0).fill('');
await independentTimeConductorInputs.nth(0).fill(startBoundString);
await page.keyboard.press('Enter');
await independentTimeConductorInputs.nth(1).fill('');
await independentTimeConductorInputs.nth(1).fill(endBoundString);
await page.keyboard.press('Enter');
expect(await activityBounds.count()).toEqual(1);
});
await test.step("Can have multiple TimeStrips with the same plan linked and different Independent Time Contexts", async () => {
// Create another Time Strip and verify that it has been created
const createdTimeStrip = await createDomainObjectWithDefaults(page, {
type: 'Time Strip',
name: "Another Time Strip"
});
const objectName = await page.locator('.l-browse-bar__object-name').innerText();
expect(objectName).toBe(createdTimeStrip.name);
// Drag the existing Plan onto the newly created Time Strip, and save.
await page.dragAndDrop(`role=treeitem[name=/${plan.name}/]`, '.c-object-view');
await page.click("button[title='Save']");
await page.click("li[title='Save and Finish Editing']");
// Activate Independent Time Conductor in Fixed Time Mode
await page.click('.c-toggle-switch__slider');
// All events should be displayed at this point because the
// initial independent context bounds will match the global bounds
expect(await activityBounds.count()).toEqual(5);
// Set the independent time bounds so that two events are shown
const startBound = testPlan.TEST_GROUP[0].start;
const endBound = testPlan.TEST_GROUP[1].end;
const startBoundString = new Date(startBound).toISOString().replace('T', ' ');
const endBoundString = new Date(endBound).toISOString().replace('T', ' ');
await independentTimeConductorInputs.nth(0).fill('');
await independentTimeConductorInputs.nth(0).fill(startBoundString);
await page.keyboard.press('Enter');
await independentTimeConductorInputs.nth(1).fill('');
await independentTimeConductorInputs.nth(1).fill(endBoundString);
await page.keyboard.press('Enter');
// Verify that two events are displayed
expect(await activityBounds.count()).toEqual(2);
// Switch to the previous Time Strip and verify that only one event is displayed
await page.goto(timestrip.url);
expect(await activityBounds.count()).toEqual(1);
});
}); });
// Constant locators
const independentTimeConductorInputs = page.locator(
'.l-shell__main-independent-time-conductor .c-input--datetime'
);
const activityBounds = page.locator('.activity-bounds');
// Goto baseURL
await page.goto('./', { waitUntil: 'domcontentloaded' });
const timestrip = await test.step('Create a Time Strip', async () => {
const createdTimeStrip = await createDomainObjectWithDefaults(page, { type: 'Time Strip' });
const objectName = await page.locator('.l-browse-bar__object-name').innerText();
expect(objectName).toBe(createdTimeStrip.name);
return createdTimeStrip;
});
const plan = await test.step('Create a Plan and add it to the timestrip', async () => {
const createdPlan = await createPlanFromJSON(page, {
name: 'Test Plan',
json: testPlan
});
await page.goto(timestrip.url);
// Expand the tree to show the plan
await page.click("button[title='Show selected item in tree']");
await page.dragAndDrop(`role=treeitem[name=/${createdPlan.name}/]`, '.c-object-view');
await page.click("button[title='Save']");
await page.click("li[title='Save and Finish Editing']");
const startBound = testPlan.TEST_GROUP[0].start;
const endBound = testPlan.TEST_GROUP[testPlan.TEST_GROUP.length - 1].end;
// Switch to fixed time mode with all plan events within the bounds
await page.goto(
`${timestrip.url}?tc.mode=fixed&tc.startBound=${startBound}&tc.endBound=${endBound}&tc.timeSystem=utc&view=time-strip.view`
);
// Verify all events are displayed
const eventCount = await page.locator('.activity-bounds').count();
expect(eventCount).toEqual(testPlan.TEST_GROUP.length);
return createdPlan;
});
await test.step('TimeStrip can use the Independent Time Conductor', async () => {
// Activate Independent Time Conductor in Fixed Time Mode
await page.click('.c-toggle-switch__slider');
expect(await activityBounds.count()).toEqual(0);
// Set the independent time bounds so that only one event is shown
const startBound = testPlan.TEST_GROUP[0].start;
const endBound = testPlan.TEST_GROUP[0].end;
const startBoundString = new Date(startBound).toISOString().replace('T', ' ');
const endBoundString = new Date(endBound).toISOString().replace('T', ' ');
await independentTimeConductorInputs.nth(0).fill('');
await independentTimeConductorInputs.nth(0).fill(startBoundString);
await page.keyboard.press('Enter');
await independentTimeConductorInputs.nth(1).fill('');
await independentTimeConductorInputs.nth(1).fill(endBoundString);
await page.keyboard.press('Enter');
expect(await activityBounds.count()).toEqual(1);
});
await test.step('Can have multiple TimeStrips with the same plan linked and different Independent Time Contexts', async () => {
// Create another Time Strip and verify that it has been created
const createdTimeStrip = await createDomainObjectWithDefaults(page, {
type: 'Time Strip',
name: 'Another Time Strip'
});
const objectName = await page.locator('.l-browse-bar__object-name').innerText();
expect(objectName).toBe(createdTimeStrip.name);
// Drag the existing Plan onto the newly created Time Strip, and save.
await page.dragAndDrop(`role=treeitem[name=/${plan.name}/]`, '.c-object-view');
await page.click("button[title='Save']");
await page.click("li[title='Save and Finish Editing']");
// Activate Independent Time Conductor in Fixed Time Mode
await page.click('.c-toggle-switch__slider');
// All events should be displayed at this point because the
// initial independent context bounds will match the global bounds
expect(await activityBounds.count()).toEqual(5);
// Set the independent time bounds so that two events are shown
const startBound = testPlan.TEST_GROUP[0].start;
const endBound = testPlan.TEST_GROUP[1].end;
const startBoundString = new Date(startBound).toISOString().replace('T', ' ');
const endBoundString = new Date(endBound).toISOString().replace('T', ' ');
await independentTimeConductorInputs.nth(0).fill('');
await independentTimeConductorInputs.nth(0).fill(startBoundString);
await page.keyboard.press('Enter');
await independentTimeConductorInputs.nth(1).fill('');
await independentTimeConductorInputs.nth(1).fill(endBoundString);
await page.keyboard.press('Enter');
// Verify that two events are displayed
expect(await activityBounds.count()).toEqual(2);
// Switch to the previous Time Strip and verify that only one event is displayed
await page.goto(timestrip.url);
expect(await activityBounds.count()).toEqual(1);
});
});
}); });

View File

@ -27,40 +27,40 @@ This test suite is dedicated to tests which verify the basic operations surround
const { test, expect } = require('../../../../baseFixtures'); const { test, expect } = require('../../../../baseFixtures');
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 ({ page }) => { page
test.info().annotations.push({ }) => {
type: 'issue', test.info().annotations.push({
description: 'https://github.com/nasa/openmct/issues/4878' type: 'issue',
}); description: 'https://github.com/nasa/openmct/issues/4878'
//Go to baseURL
await page.goto('./', { waitUntil: 'domcontentloaded' });
//Click the Create button
await page.click('button:has-text("Create")');
// Click Clock
await page.click('text=Clock');
// Click .icon-arrow-down
await page.locator('.icon-arrow-down').click();
//verify if the autocomplete dropdown is visible
await expect(page.locator(".c-input--autocomplete__options")).toBeVisible();
// Click .icon-arrow-down
await page.locator('.icon-arrow-down').click();
// Verify clicking on the autocomplete arrow collapses the dropdown
await expect(page.locator(".c-input--autocomplete__options")).toBeHidden();
// Click timezone input to open dropdown
await page.locator('.c-input--autocomplete__input').click();
//verify if the autocomplete dropdown is visible
await expect(page.locator(".c-input--autocomplete__options")).toBeVisible();
// Verify clicking outside the autocomplete dropdown collapses it
await page.locator('text=Timezone').click();
// Verify clicking on the autocomplete arrow collapses the dropdown
await expect(page.locator(".c-input--autocomplete__options")).toBeHidden();
}); });
//Go to baseURL
await page.goto('./', { waitUntil: 'domcontentloaded' });
//Click the Create button
await page.click('button:has-text("Create")');
// Click Clock
await page.click('text=Clock');
// Click .icon-arrow-down
await page.locator('.icon-arrow-down').click();
//verify if the autocomplete dropdown is visible
await expect(page.locator('.c-input--autocomplete__options')).toBeVisible();
// Click .icon-arrow-down
await page.locator('.icon-arrow-down').click();
// Verify clicking on the autocomplete arrow collapses the dropdown
await expect(page.locator('.c-input--autocomplete__options')).toBeHidden();
// Click timezone input to open dropdown
await page.locator('.c-input--autocomplete__input').click();
//verify if the autocomplete dropdown is visible
await expect(page.locator('.c-input--autocomplete__options')).toBeVisible();
// Verify clicking outside the autocomplete dropdown collapses it
await page.locator('text=Timezone').click();
// Verify clicking on the autocomplete arrow collapses the dropdown
await expect(page.locator('.c-input--autocomplete__options')).toBeHidden();
});
}); });

View File

@ -25,17 +25,17 @@
const { test, expect } = require('../../../../baseFixtures'); const { test, expect } = require('../../../../baseFixtures');
test.describe('Remote Clock', () => { test.describe('Remote Clock', () => {
// eslint-disable-next-line require-await // eslint-disable-next-line require-await
test.fixme('blocks historical requests until first tick is received', async ({ page }) => { test.fixme('blocks historical requests until first tick is received', async ({ page }) => {
test.info().annotations.push({ test.info().annotations.push({
type: 'issue', type: 'issue',
description: 'https://github.com/nasa/openmct/issues/5221' description: 'https://github.com/nasa/openmct/issues/5221'
});
// addInitScript to with remote clock
// Switch time conductor mode to 'remote clock'
// Navigate to telemetry
// Verify that the plot renders historical data within the correct bounds
// Refresh the page
// Verify again that the plot renders historical data within the correct bounds
}); });
// addInitScript to with remote clock
// Switch time conductor mode to 'remote clock'
// Navigate to telemetry
// Verify that the plot renders historical data within the correct bounds
// Refresh the page
// Verify again that the plot renders historical data within the correct bounds
});
}); });

View File

@ -33,293 +33,336 @@ let conditionSetUrl;
let getConditionSetIdentifierFromUrl; let getConditionSetIdentifierFromUrl;
test.describe.serial('Condition Set CRUD Operations on @localStorage', () => { test.describe.serial('Condition Set CRUD Operations on @localStorage', () => {
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.click('button:has-text("Create")');
await page.locator('li[role="menuitem"]:has-text("Condition Set")').click(); await page.locator('li[role="menuitem"]:has-text("Condition Set")').click();
await Promise.all([ await Promise.all([page.waitForNavigation(), page.click('button:has-text("OK")')]);
page.waitForNavigation(),
page.click('button:has-text("OK")')
]);
//Save localStorage for future test execution //Save localStorage for future test execution
await context.storageState({ path: './e2e/test-data/recycled_local_storage.json' }); await context.storageState({ path: './e2e/test-data/recycled_local_storage.json' });
//Set object identifier from url //Set object identifier from url
conditionSetUrl = page.url(); conditionSetUrl = page.url();
getConditionSetIdentifierFromUrl = conditionSetUrl.split('/').pop().split('?')[0]; getConditionSetIdentifierFromUrl = conditionSetUrl.split('/').pop().split('?')[0];
console.debug(`getConditionSetIdentifierFromUrl: ${getConditionSetIdentifierFromUrl}`); console.debug(`getConditionSetIdentifierFromUrl: ${getConditionSetIdentifierFromUrl}`);
await page.close(); await page.close();
}); });
//Load localStorage for subsequent tests //Load localStorage for subsequent tests
test.use({ storageState: './e2e/test-data/recycled_local_storage.json' }); test.use({ storageState: './e2e/test-data/recycled_local_storage.json' });
//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 ({ page }) => { test('Condition set object properties persist in main view and inspector @localStorage', async ({
//Navigate to baseURL with injected localStorage page
await page.goto(conditionSetUrl, { waitUntil: 'networkidle' }); }) => {
//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.soft(page.locator('.l-browse-bar__object-name')).toContainText('Unnamed Condition Set'); await expect
.soft(page.locator('.l-browse-bar__object-name'))
.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([ await Promise.all([page.reload(), page.waitForLoadState('networkidle')]);
page.reload(),
page.waitForLoadState('networkidle')
]);
//Re-verify after reload //Re-verify after reload
await expect.soft(page.locator('.l-browse-bar__object-name')).toContainText('Unnamed Condition Set'); await expect
//Assertions on loaded Condition Set in Inspector .soft(page.locator('.l-browse-bar__object-name'))
expect.soft(page.locator('_vue=item.name=Unnamed Condition Set')).toBeTruthy(); .toContainText('Unnamed Condition Set');
//Assertions on loaded Condition Set in Inspector
expect.soft(page.locator('_vue=item.name=Unnamed Condition Set')).toBeTruthy();
});
test('condition set object can be modified on @localStorage', async ({ page, openmctConfig }) => {
const { myItemsFolderName } = openmctConfig;
}); await page.goto(conditionSetUrl, { waitUntil: 'networkidle' });
test('condition set object can be modified on @localStorage', async ({ page, openmctConfig }) => {
const { myItemsFolderName } = openmctConfig;
await page.goto(conditionSetUrl, { waitUntil: 'networkidle' }); //Assertions on loaded Condition Set in main view. This is a stateful transition step after page.goto()
await expect
.soft(page.locator('.l-browse-bar__object-name'))
.toContainText('Unnamed Condition Set');
//Assertions on loaded Condition Set in main view. This is a stateful transition step after page.goto() //Update the Condition Set properties
await expect.soft(page.locator('.l-browse-bar__object-name')).toContainText('Unnamed Condition Set'); // Click Edit Button
await page.locator('text=Conditions View Snapshot >> button').nth(3).click();
//Update the Condition Set properties //Edit Condition Set Name from main view
// Click Edit Button await page
await page.locator('text=Conditions View Snapshot >> button').nth(3).click(); .locator('.l-browse-bar__object-name')
.filter({ hasText: 'Unnamed Condition Set' })
.first()
.fill('Renamed Condition Set');
await page
.locator('.l-browse-bar__object-name')
.filter({ hasText: 'Renamed Condition Set' })
.first()
.press('Enter');
// Click Save Button
await page
.locator('text=Snapshot Save and Finish Editing Save and Continue Editing >> button')
.nth(1)
.click();
// Click Save and Finish Editing Option
await page.locator('text=Save and Finish Editing').click();
//Edit Condition Set Name from main view //Verify Main section reflects updated Name Property
await page.locator('.l-browse-bar__object-name').filter({ hasText: 'Unnamed Condition Set' }).first().fill('Renamed Condition Set'); await expect
await page.locator('.l-browse-bar__object-name').filter({ hasText: 'Renamed Condition Set' }).first().press('Enter'); .soft(page.locator('.l-browse-bar__object-name'))
// Click Save Button .toContainText('Renamed Condition Set');
await page.locator('text=Snapshot Save and Finish Editing Save and Continue Editing >> button').nth(1).click();
// Click Save and Finish Editing Option
await page.locator('text=Save and Finish Editing').click();
//Verify Main section reflects updated Name Property // Verify Inspector properties
await expect.soft(page.locator('.l-browse-bar__object-name')).toContainText('Renamed Condition Set'); // Verify Inspector has updated Name property
expect.soft(page.locator('text=Renamed Condition Set').nth(1)).toBeTruthy();
// Verify Inspector Details has updated Name property
expect.soft(page.locator('text=Renamed Condition Set').nth(2)).toBeTruthy();
// Verify Inspector properties // Verify Tree reflects updated Name proprety
// Verify Inspector has updated Name property // Expand Tree
expect.soft(page.locator('text=Renamed Condition Set').nth(1)).toBeTruthy(); await page.locator(`text=Open MCT ${myItemsFolderName} >> span >> nth=3`).click();
// Verify Inspector Details has updated Name property // Verify Condition Set Object is renamed in Tree
expect.soft(page.locator('text=Renamed Condition Set').nth(2)).toBeTruthy(); expect(page.locator('a:has-text("Renamed Condition Set")')).toBeTruthy();
// Verify Search Tree reflects renamed Name property
await page.locator('[aria-label="OpenMCT Search"] input[type="search"]').fill('Renamed');
expect(page.locator('a:has-text("Renamed Condition Set")')).toBeTruthy();
// Verify Tree reflects updated Name proprety //Reload Page
// Expand Tree await Promise.all([page.reload(), page.waitForLoadState('networkidle')]);
await page.locator(`text=Open MCT ${myItemsFolderName} >> span >> nth=3`).click();
// Verify Condition Set Object is renamed in Tree
expect(page.locator('a:has-text("Renamed Condition Set")')).toBeTruthy();
// Verify Search Tree reflects renamed Name property
await page.locator('[aria-label="OpenMCT Search"] input[type="search"]').fill('Renamed');
expect(page.locator('a:has-text("Renamed Condition Set")')).toBeTruthy();
//Reload Page //Verify Main section reflects updated Name Property
await Promise.all([ await expect
page.reload(), .soft(page.locator('.l-browse-bar__object-name'))
page.waitForLoadState('networkidle') .toContainText('Renamed Condition Set');
]);
//Verify Main section reflects updated Name Property // Verify Inspector properties
await expect.soft(page.locator('.l-browse-bar__object-name')).toContainText('Renamed Condition Set'); // Verify Inspector has updated Name property
expect.soft(page.locator('text=Renamed Condition Set').nth(1)).toBeTruthy();
// Verify Inspector Details has updated Name property
expect.soft(page.locator('text=Renamed Condition Set').nth(2)).toBeTruthy();
// Verify Inspector properties // Verify Tree reflects updated Name proprety
// Verify Inspector has updated Name property // Expand Tree
expect.soft(page.locator('text=Renamed Condition Set').nth(1)).toBeTruthy(); await page.locator(`text=Open MCT ${myItemsFolderName} >> span >> nth=3`).click();
// Verify Inspector Details has updated Name property // Verify Condition Set Object is renamed in Tree
expect.soft(page.locator('text=Renamed Condition Set').nth(2)).toBeTruthy(); expect(page.locator('a:has-text("Renamed Condition Set")')).toBeTruthy();
// Verify Search Tree reflects renamed Name property
await page.locator('[aria-label="OpenMCT Search"] input[type="search"]').fill('Renamed');
expect(page.locator('a:has-text("Renamed Condition Set")')).toBeTruthy();
});
test('condition set object can be deleted by Search Tree Actions menu on @localStorage', async ({
page
}) => {
//Navigate to baseURL
await page.goto('./', { waitUntil: 'domcontentloaded' });
// Verify Tree reflects updated Name proprety //Assertions on loaded Condition Set in main view. This is a stateful transition step after page.goto()
// Expand Tree await expect(
await page.locator(`text=Open MCT ${myItemsFolderName} >> span >> nth=3`).click(); page.locator('a:has-text("Unnamed Condition Set Condition Set") >> nth=0')
// Verify Condition Set Object is renamed in Tree ).toBeVisible();
expect(page.locator('a:has-text("Renamed Condition Set")')).toBeTruthy();
// Verify Search Tree reflects renamed Name property
await page.locator('[aria-label="OpenMCT Search"] input[type="search"]').fill('Renamed');
expect(page.locator('a:has-text("Renamed Condition Set")')).toBeTruthy();
});
test('condition set object can be deleted by Search Tree Actions menu on @localStorage', async ({ page }) => {
//Navigate to baseURL
await page.goto('./', { waitUntil: 'domcontentloaded' });
//Assertions on loaded Condition Set in main view. This is a stateful transition step after page.goto() const numberOfConditionSetsToStart = await page
await expect(page.locator('a:has-text("Unnamed Condition Set Condition Set") >> nth=0')).toBeVisible(); .locator('a:has-text("Unnamed Condition Set Condition Set")')
.count();
const numberOfConditionSetsToStart = await page.locator('a:has-text("Unnamed Condition Set Condition Set")').count(); // Search for Unnamed Condition Set
await page
.locator('[aria-label="OpenMCT Search"] input[type="search"]')
.fill('Unnamed Condition Set');
// Click Search Result
await page
.locator('[aria-label="OpenMCT Search"] >> text=Unnamed Condition Set')
.first()
.click();
// Click hamburger button
await page.locator('[title="More options"]').click();
// Search for Unnamed Condition Set // Click 'Remove' and press OK
await page.locator('[aria-label="OpenMCT Search"] input[type="search"]').fill('Unnamed Condition Set'); await page.locator('li[role="menuitem"]:has-text("Remove")').click();
// Click Search Result await page.locator('button:has-text("OK")').click();
await page.locator('[aria-label="OpenMCT Search"] >> text=Unnamed Condition Set').first().click();
// Click hamburger button
await page.locator('[title="More options"]').click();
// Click 'Remove' and press OK //Expect Unnamed Condition Set to be removed in Main View
await page.locator('li[role="menuitem"]:has-text("Remove")').click(); const numberOfConditionSetsAtEnd = await page
await page.locator('button:has-text("OK")').click(); .locator('a:has-text("Unnamed Condition Set Condition Set")')
.count();
//Expect Unnamed Condition Set to be removed in Main View expect(numberOfConditionSetsAtEnd).toEqual(numberOfConditionSetsToStart - 1);
const numberOfConditionSetsAtEnd = await page.locator('a:has-text("Unnamed Condition Set Condition Set")').count();
expect(numberOfConditionSetsAtEnd).toEqual(numberOfConditionSetsToStart - 1); //Feature?
//Domain Object is still available by direct URL after delete
//Feature? await page.goto(conditionSetUrl, { waitUntil: 'networkidle' });
//Domain Object is still available by direct URL after delete await expect(page.locator('.l-browse-bar__object-name')).toContainText('Unnamed Condition Set');
await page.goto(conditionSetUrl, { waitUntil: 'networkidle' }); });
await expect(page.locator('.l-browse-bar__object-name')).toContainText('Unnamed Condition Set');
});
}); });
test.describe('Basic Condition Set Use', () => { test.describe('Basic Condition Set Use', () => {
test.beforeEach(async ({ page }) => { test.beforeEach(async ({ page }) => {
// Open a browser, navigate to the main page, and wait until all network events to resolve // Open a browser, navigate to the main page, and wait until all network events to resolve
await page.goto('./', { waitUntil: 'domcontentloaded' }); await page.goto('./', { waitUntil: 'domcontentloaded' });
});
test('Can add a condition', async ({ page }) => {
// Create a new condition set
await createDomainObjectWithDefaults(page, {
type: 'Condition Set',
name: 'Test Condition Set'
}); });
test('Can add a condition', async ({ page }) => { // Change the object to edit mode
// Create a new condition set await page.locator('[title="Edit"]').click();
await createDomainObjectWithDefaults(page, {
type: 'Condition Set',
name: "Test Condition Set"
});
// Change the object to edit mode
await page.locator('[title="Edit"]').click();
// Click Add Condition button // Click Add Condition button
await page.locator('#addCondition').click(); await page.locator('#addCondition').click();
// Check that the new Unnamed Condition section appears // Check that the new Unnamed Condition section appears
const numOfUnnamedConditions = await page.locator('text=Unnamed Condition').count(); const numOfUnnamedConditions = await page.locator('text=Unnamed Condition').count();
expect(numOfUnnamedConditions).toEqual(1); expect(numOfUnnamedConditions).toEqual(1);
});
test('ConditionSet should display appropriate view options', async ({ page }) => {
test.info().annotations.push({
type: 'issue',
description: 'https://github.com/nasa/openmct/issues/5924'
}); });
test('ConditionSet should display appropriate view options', async ({ page }) => {
test.info().annotations.push({
type: 'issue',
description: 'https://github.com/nasa/openmct/issues/5924'
});
await createDomainObjectWithDefaults(page, { await createDomainObjectWithDefaults(page, {
type: 'Sine Wave Generator', type: 'Sine Wave Generator',
name: "Alpha Sine Wave Generator" name: 'Alpha Sine Wave Generator'
});
await createDomainObjectWithDefaults(page, {
type: 'Sine Wave Generator',
name: "Beta Sine Wave Generator"
});
const conditionSet1 = await createDomainObjectWithDefaults(page, {
type: 'Condition Set',
name: "Test Condition Set"
});
// Change the object to edit mode
await page.locator('[title="Edit"]').click();
// Expand the 'My Items' folder in the left tree
await page.goto(conditionSet1.url);
page.click('button[title="Show selected item in tree"]');
// Add the Alpha & Beta Sine Wave Generator to the Condition Set and save changes
const treePane = page.getByRole('tree', {
name: 'Main Tree'
});
const alphaGeneratorTreeItem = treePane.getByRole('treeitem', { name: "Alpha Sine Wave Generator"});
const betaGeneratorTreeItem = treePane.getByRole('treeitem', { name: "Beta Sine Wave Generator"});
const conditionCollection = page.locator('#conditionCollection');
await alphaGeneratorTreeItem.dragTo(conditionCollection);
await betaGeneratorTreeItem.dragTo(conditionCollection);
const saveButtonLocator = page.locator('button[title="Save"]');
await saveButtonLocator.click();
await page.getByRole('listitem', { name: 'Save and Finish Editing' }).click();
await page.click('button[title="Change the current view"]');
await expect(page.getByRole('menuitem', { name: /Lad Table/ })).toBeHidden();
await expect(page.getByRole('menuitem', { name: /Conditions View/ })).toBeVisible();
await expect(page.getByRole('menuitem', { name: /Plot/ })).toBeVisible();
await expect(page.getByRole('menuitem', { name: /Telemetry Table/ })).toBeVisible();
}); });
test('ConditionSet should output blank instead of the default value', async ({ page }) => { await createDomainObjectWithDefaults(page, {
//Navigate to baseURL type: 'Sine Wave Generator',
await page.goto('./', { waitUntil: 'domcontentloaded' }); name: 'Beta Sine Wave Generator'
//Click the Create button
await page.click('button:has-text("Create")');
// Click the object specified by 'type'
await page.click(`li[role='menuitem']:text("Sine Wave Generator")`);
await page.getByRole('spinbutton', { name: 'Loading Delay (ms)' }).fill('8000');
const nameInput = page.locator('form[name="mctForm"] .first input[type="text"]');
await nameInput.fill("Delayed Sine Wave Generator");
// Click OK button and wait for Navigate event
await Promise.all([
page.waitForLoadState(),
page.click('[aria-label="Save"]'),
// Wait for Save Banner to appear
page.waitForSelector('.c-message-banner__message')
]);
// Create a new condition set
await createDomainObjectWithDefaults(page, {
type: 'Condition Set',
name: "Test Blank Output of Condition Set"
});
// Change the object to edit mode
await page.locator('[title="Edit"]').click();
// Click Add Condition button twice
await page.locator('#addCondition').click();
await page.locator('#addCondition').click();
await page.locator('#conditionCollection').getByRole('textbox').nth(0).fill('First Condition');
await page.locator('#conditionCollection').getByRole('textbox').nth(1).fill('Second Condition');
// Expand the 'My Items' folder in the left tree
await page.locator('.c-tree__item__view-control.c-disclosure-triangle').first().click();
// Add the Sine Wave Generator to the Condition Set and save changes
const treePane = page.getByRole('tree', {
name: 'Main Tree'
});
const sineWaveGeneratorTreeItem = treePane.getByRole('treeitem', { name: "Delayed Sine Wave Generator"});
const conditionCollection = await page.locator('#conditionCollection');
await sineWaveGeneratorTreeItem.dragTo(conditionCollection);
const firstCriterionTelemetry = await page.locator('[aria-label="Criterion Telemetry Selection"] >> nth=0');
firstCriterionTelemetry.selectOption({ label: 'Delayed Sine Wave Generator' });
const secondCriterionTelemetry = await page.locator('[aria-label="Criterion Telemetry Selection"] >> nth=1');
secondCriterionTelemetry.selectOption({ label: 'Delayed Sine Wave Generator' });
const firstCriterionMetadata = await page.locator('[aria-label="Criterion Metadata Selection"] >> nth=0');
firstCriterionMetadata.selectOption({ label: 'Sine' });
const secondCriterionMetadata = await page.locator('[aria-label="Criterion Metadata Selection"] >> nth=1');
secondCriterionMetadata.selectOption({ label: 'Sine' });
const firstCriterionComparison = await page.locator('[aria-label="Criterion Comparison Selection"] >> nth=0');
firstCriterionComparison.selectOption({ label: 'is greater than or equal to' });
const secondCriterionComparison = await page.locator('[aria-label="Criterion Comparison Selection"] >> nth=1');
secondCriterionComparison.selectOption({ label: 'is less than' });
const firstCriterionInput = await page.locator('[aria-label="Criterion Input"] >> nth=0');
await firstCriterionInput.fill("0");
const secondCriterionInput = await page.locator('[aria-label="Criterion Input"] >> nth=1');
await secondCriterionInput.fill("0");
const saveButtonLocator = page.locator('button[title="Save"]');
await saveButtonLocator.click();
await page.getByRole('listitem', { name: 'Save and Finish Editing' }).click();
const outputValue = await page.locator('[aria-label="Current Output Value"]');
await expect(outputValue).toHaveText('---');
}); });
const conditionSet1 = await createDomainObjectWithDefaults(page, {
type: 'Condition Set',
name: 'Test Condition Set'
});
// Change the object to edit mode
await page.locator('[title="Edit"]').click();
// Expand the 'My Items' folder in the left tree
await page.goto(conditionSet1.url);
page.click('button[title="Show selected item in tree"]');
// Add the Alpha & Beta Sine Wave Generator to the Condition Set and save changes
const treePane = page.getByRole('tree', {
name: 'Main Tree'
});
const alphaGeneratorTreeItem = treePane.getByRole('treeitem', {
name: 'Alpha Sine Wave Generator'
});
const betaGeneratorTreeItem = treePane.getByRole('treeitem', {
name: 'Beta Sine Wave Generator'
});
const conditionCollection = page.locator('#conditionCollection');
await alphaGeneratorTreeItem.dragTo(conditionCollection);
await betaGeneratorTreeItem.dragTo(conditionCollection);
const saveButtonLocator = page.locator('button[title="Save"]');
await saveButtonLocator.click();
await page.getByRole('listitem', { name: 'Save and Finish Editing' }).click();
await page.click('button[title="Change the current view"]');
await expect(page.getByRole('menuitem', { name: /Lad Table/ })).toBeHidden();
await expect(page.getByRole('menuitem', { name: /Conditions View/ })).toBeVisible();
await expect(page.getByRole('menuitem', { name: /Plot/ })).toBeVisible();
await expect(page.getByRole('menuitem', { name: /Telemetry Table/ })).toBeVisible();
});
test('ConditionSet should output blank instead of the default value', async ({ page }) => {
//Navigate to baseURL
await page.goto('./', { waitUntil: 'domcontentloaded' });
//Click the Create button
await page.click('button:has-text("Create")');
// Click the object specified by 'type'
await page.click(`li[role='menuitem']:text("Sine Wave Generator")`);
await page.getByRole('spinbutton', { name: 'Loading Delay (ms)' }).fill('8000');
const nameInput = page.locator('form[name="mctForm"] .first input[type="text"]');
await nameInput.fill('Delayed Sine Wave Generator');
// Click OK button and wait for Navigate event
await Promise.all([
page.waitForLoadState(),
page.click('[aria-label="Save"]'),
// Wait for Save Banner to appear
page.waitForSelector('.c-message-banner__message')
]);
// Create a new condition set
await createDomainObjectWithDefaults(page, {
type: 'Condition Set',
name: 'Test Blank Output of Condition Set'
});
// Change the object to edit mode
await page.locator('[title="Edit"]').click();
// Click Add Condition button twice
await page.locator('#addCondition').click();
await page.locator('#addCondition').click();
await page.locator('#conditionCollection').getByRole('textbox').nth(0).fill('First Condition');
await page.locator('#conditionCollection').getByRole('textbox').nth(1).fill('Second Condition');
// Expand the 'My Items' folder in the left tree
await page.locator('.c-tree__item__view-control.c-disclosure-triangle').first().click();
// Add the Sine Wave Generator to the Condition Set and save changes
const treePane = page.getByRole('tree', {
name: 'Main Tree'
});
const sineWaveGeneratorTreeItem = treePane.getByRole('treeitem', {
name: 'Delayed Sine Wave Generator'
});
const conditionCollection = await page.locator('#conditionCollection');
await sineWaveGeneratorTreeItem.dragTo(conditionCollection);
const firstCriterionTelemetry = await page.locator(
'[aria-label="Criterion Telemetry Selection"] >> nth=0'
);
firstCriterionTelemetry.selectOption({ label: 'Delayed Sine Wave Generator' });
const secondCriterionTelemetry = await page.locator(
'[aria-label="Criterion Telemetry Selection"] >> nth=1'
);
secondCriterionTelemetry.selectOption({ label: 'Delayed Sine Wave Generator' });
const firstCriterionMetadata = await page.locator(
'[aria-label="Criterion Metadata Selection"] >> nth=0'
);
firstCriterionMetadata.selectOption({ label: 'Sine' });
const secondCriterionMetadata = await page.locator(
'[aria-label="Criterion Metadata Selection"] >> nth=1'
);
secondCriterionMetadata.selectOption({ label: 'Sine' });
const firstCriterionComparison = await page.locator(
'[aria-label="Criterion Comparison Selection"] >> nth=0'
);
firstCriterionComparison.selectOption({ label: 'is greater than or equal to' });
const secondCriterionComparison = await page.locator(
'[aria-label="Criterion Comparison Selection"] >> nth=1'
);
secondCriterionComparison.selectOption({ label: 'is less than' });
const firstCriterionInput = await page.locator('[aria-label="Criterion Input"] >> nth=0');
await firstCriterionInput.fill('0');
const secondCriterionInput = await page.locator('[aria-label="Criterion Input"] >> nth=1');
await secondCriterionInput.fill('0');
const saveButtonLocator = page.locator('button[title="Save"]');
await saveButtonLocator.click();
await page.getByRole('listitem', { name: 'Save and Finish Editing' }).click();
const outputValue = await page.locator('[aria-label="Current Output Value"]');
await expect(outputValue).toHaveText('---');
});
}); });

View File

@ -21,173 +21,190 @@
*****************************************************************************/ *****************************************************************************/
const { test, expect } = require('../../../../pluginFixtures'); const { test, expect } = require('../../../../pluginFixtures');
const { createDomainObjectWithDefaults, setStartOffset, setFixedTimeMode, setRealTimeMode } = require('../../../../appActions'); const {
createDomainObjectWithDefaults,
setStartOffset,
setFixedTimeMode,
setRealTimeMode
} = require('../../../../appActions');
test.describe('Display Layout', () => { test.describe('Display Layout', () => {
/** @type {import('../../../../appActions').CreatedObjectInfo} */ /** @type {import('../../../../appActions').CreatedObjectInfo} */
let sineWaveObject; let sineWaveObject;
test.beforeEach(async ({ page }) => { test.beforeEach(async ({ page }) => {
await page.goto('./', { waitUntil: 'domcontentloaded' }); await page.goto('./', { waitUntil: 'domcontentloaded' });
await setRealTimeMode(page); await setRealTimeMode(page);
// Create Sine Wave Generator // Create Sine Wave Generator
sineWaveObject = await createDomainObjectWithDefaults(page, { sineWaveObject = await createDomainObjectWithDefaults(page, {
type: 'Sine Wave Generator' type: 'Sine Wave Generator'
});
}); });
test('alpha-numeric widget telemetry value exactly matches latest telemetry value received in real time', async ({ page }) => { });
// Create a Display Layout test('alpha-numeric widget telemetry value exactly matches latest telemetry value received in real time', async ({
await createDomainObjectWithDefaults(page, { page
type: 'Display Layout', }) => {
name: "Test Display Layout" // Create a Display Layout
}); await createDomainObjectWithDefaults(page, {
// Edit Display Layout type: 'Display Layout',
await page.locator('[title="Edit"]').click(); name: 'Test Display Layout'
// Expand the 'My Items' folder in the left tree
await page.locator('.c-tree__item__view-control.c-disclosure-triangle').click();
// Add the Sine Wave Generator to the Display Layout and save changes
const treePane = page.getByRole('tree', {
name: 'Main Tree'
});
const sineWaveGeneratorTreeItem = treePane.getByRole('treeitem', {
name: new RegExp(sineWaveObject.name)
});
const layoutGridHolder = page.locator('.l-layout__grid-holder');
await sineWaveGeneratorTreeItem.dragTo(layoutGridHolder);
await page.locator('button[title="Save"]').click();
await page.locator('text=Save and Finish Editing').click();
// Subscribe to the Sine Wave Generator data
// On getting data, check if the value found in the Display Layout is the most recent value
// from the Sine Wave Generator
const getTelemValuePromise = await subscribeToTelemetry(page, sineWaveObject.uuid);
const formattedTelemetryValue = getTelemValuePromise;
const displayLayoutValuePromise = await page.waitForSelector(`text="${formattedTelemetryValue}"`);
const displayLayoutValue = await displayLayoutValuePromise.textContent();
const trimmedDisplayValue = displayLayoutValue.trim();
expect(trimmedDisplayValue).toBe(formattedTelemetryValue);
}); });
test('alpha-numeric widget telemetry value exactly matches latest telemetry value received in fixed time', async ({ page }) => { // Edit Display Layout
// Create a Display Layout await page.locator('[title="Edit"]').click();
await createDomainObjectWithDefaults(page, {
type: 'Display Layout',
name: "Test Display Layout"
});
// Edit Display Layout
await page.locator('[title="Edit"]').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();
// Add the Sine Wave Generator to the Display Layout and save changes // Add the Sine Wave Generator to the Display Layout and save changes
const treePane = page.getByRole('tree', { const treePane = page.getByRole('tree', {
name: 'Main Tree' name: 'Main Tree'
});
const sineWaveGeneratorTreeItem = treePane.getByRole('treeitem', {
name: new RegExp(sineWaveObject.name)
});
const layoutGridHolder = page.locator('.l-layout__grid-holder');
await sineWaveGeneratorTreeItem.dragTo(layoutGridHolder);
await page.locator('button[title="Save"]').click();
await page.locator('text=Save and Finish Editing').click();
// Subscribe to the Sine Wave Generator data
const getTelemValuePromise = await subscribeToTelemetry(page, sineWaveObject.uuid);
// Set an offset of 1 minute and then change the time mode to fixed to set a 1 minute historical window
await setStartOffset(page, { mins: '1' });
await setFixedTimeMode(page);
// On getting data, check if the value found in the Display Layout is the most recent value
// from the Sine Wave Generator
const formattedTelemetryValue = getTelemValuePromise;
const displayLayoutValuePromise = await page.waitForSelector(`text="${formattedTelemetryValue}"`);
const displayLayoutValue = await displayLayoutValuePromise.textContent();
const trimmedDisplayValue = displayLayoutValue.trim();
expect(trimmedDisplayValue).toBe(formattedTelemetryValue);
}); });
test('items in a display layout can be removed with object tree context menu when viewing the display layout', async ({ page }) => { const sineWaveGeneratorTreeItem = treePane.getByRole('treeitem', {
// Create a Display Layout name: new RegExp(sineWaveObject.name)
await createDomainObjectWithDefaults(page, {
type: 'Display Layout',
name: "Test Display Layout"
});
// Edit Display Layout
await page.locator('[title="Edit"]').click();
// Expand the 'My Items' folder in the left tree
await page.locator('.c-tree__item__view-control.c-disclosure-triangle').click();
// Add the Sine Wave Generator to the Display Layout and save changes
const treePane = page.getByRole('tree', {
name: 'Main Tree'
});
const sineWaveGeneratorTreeItem = treePane.getByRole('treeitem', {
name: new RegExp(sineWaveObject.name)
});
const layoutGridHolder = page.locator('.l-layout__grid-holder');
await sineWaveGeneratorTreeItem.dragTo(layoutGridHolder);
await page.locator('button[title="Save"]').click();
await page.locator('text=Save and Finish Editing').click();
expect.soft(await page.locator('.l-layout .l-layout__frame').count()).toEqual(1);
// Expand the Display Layout so we can remove the sine wave generator
await page.locator('.c-tree__item.is-navigated-object .c-disclosure-triangle').click();
// Bring up context menu and remove
await sineWaveGeneratorTreeItem.nth(1).click({ button: 'right' });
await page.locator('li[role="menuitem"]:has-text("Remove")').click();
await page.locator('button:has-text("OK")').click();
// delete
expect(await page.locator('.l-layout .l-layout__frame').count()).toEqual(0);
}); });
test('items in a display layout can be removed with object tree context menu when viewing another item', async ({ page }) => { const layoutGridHolder = page.locator('.l-layout__grid-holder');
test.info().annotations.push({ await sineWaveGeneratorTreeItem.dragTo(layoutGridHolder);
type: 'issue', await page.locator('button[title="Save"]').click();
description: 'https://github.com/nasa/openmct/issues/3117' await page.locator('text=Save and Finish Editing').click();
});
// Create a Display Layout
const displayLayout = await createDomainObjectWithDefaults(page, {
type: 'Display Layout'
});
// Edit Display Layout
await page.locator('[title="Edit"]').click();
// Expand the 'My Items' folder in the left tree // Subscribe to the Sine Wave Generator data
await page.locator('.c-tree__item__view-control.c-disclosure-triangle').click(); // On getting data, check if the value found in the Display Layout is the most recent value
// Add the Sine Wave Generator to the Display Layout and save changes // from the Sine Wave Generator
const treePane = page.getByRole('tree', { const getTelemValuePromise = await subscribeToTelemetry(page, sineWaveObject.uuid);
name: 'Main Tree' const formattedTelemetryValue = getTelemValuePromise;
}); const displayLayoutValuePromise = await page.waitForSelector(
const sineWaveGeneratorTreeItem = treePane.getByRole('treeitem', { `text="${formattedTelemetryValue}"`
name: new RegExp(sineWaveObject.name) );
}); const displayLayoutValue = await displayLayoutValuePromise.textContent();
const layoutGridHolder = page.locator('.l-layout__grid-holder'); const trimmedDisplayValue = displayLayoutValue.trim();
await sineWaveGeneratorTreeItem.dragTo(layoutGridHolder);
await page.locator('button[title="Save"]').click();
await page.locator('text=Save and Finish Editing').click();
expect.soft(await page.locator('.l-layout .l-layout__frame').count()).toEqual(1); expect(trimmedDisplayValue).toBe(formattedTelemetryValue);
});
// Expand the Display Layout so we can remove the sine wave generator test('alpha-numeric widget telemetry value exactly matches latest telemetry value received in fixed time', async ({
await page.locator('.c-tree__item.is-navigated-object .c-disclosure-triangle').click(); page
}) => {
// Go to the original Sine Wave Generator to navigate away from the Display Layout // Create a Display Layout
await page.goto(sineWaveObject.url); await createDomainObjectWithDefaults(page, {
type: 'Display Layout',
// Bring up context menu and remove name: 'Test Display Layout'
await sineWaveGeneratorTreeItem.first().click({ button: 'right' });
await page.locator('li[role="menuitem"]:has-text("Remove")').click();
await page.locator('button:has-text("OK")').click();
// navigate back to the display layout to confirm it has been removed
await page.goto(displayLayout.url);
expect(await page.locator('.l-layout .l-layout__frame').count()).toEqual(0);
}); });
// Edit Display Layout
await page.locator('[title="Edit"]').click();
// Expand the 'My Items' folder in the left tree
await page.locator('.c-tree__item__view-control.c-disclosure-triangle').click();
// Add the Sine Wave Generator to the Display Layout and save changes
const treePane = page.getByRole('tree', {
name: 'Main Tree'
});
const sineWaveGeneratorTreeItem = treePane.getByRole('treeitem', {
name: new RegExp(sineWaveObject.name)
});
const layoutGridHolder = page.locator('.l-layout__grid-holder');
await sineWaveGeneratorTreeItem.dragTo(layoutGridHolder);
await page.locator('button[title="Save"]').click();
await page.locator('text=Save and Finish Editing').click();
// Subscribe to the Sine Wave Generator data
const getTelemValuePromise = await subscribeToTelemetry(page, sineWaveObject.uuid);
// Set an offset of 1 minute and then change the time mode to fixed to set a 1 minute historical window
await setStartOffset(page, { mins: '1' });
await setFixedTimeMode(page);
// On getting data, check if the value found in the Display Layout is the most recent value
// from the Sine Wave Generator
const formattedTelemetryValue = getTelemValuePromise;
const displayLayoutValuePromise = await page.waitForSelector(
`text="${formattedTelemetryValue}"`
);
const displayLayoutValue = await displayLayoutValuePromise.textContent();
const trimmedDisplayValue = displayLayoutValue.trim();
expect(trimmedDisplayValue).toBe(formattedTelemetryValue);
});
test('items in a display layout can be removed with object tree context menu when viewing the display layout', async ({
page
}) => {
// Create a Display Layout
await createDomainObjectWithDefaults(page, {
type: 'Display Layout',
name: 'Test Display Layout'
});
// Edit Display Layout
await page.locator('[title="Edit"]').click();
// Expand the 'My Items' folder in the left tree
await page.locator('.c-tree__item__view-control.c-disclosure-triangle').click();
// Add the Sine Wave Generator to the Display Layout and save changes
const treePane = page.getByRole('tree', {
name: 'Main Tree'
});
const sineWaveGeneratorTreeItem = treePane.getByRole('treeitem', {
name: new RegExp(sineWaveObject.name)
});
const layoutGridHolder = page.locator('.l-layout__grid-holder');
await sineWaveGeneratorTreeItem.dragTo(layoutGridHolder);
await page.locator('button[title="Save"]').click();
await page.locator('text=Save and Finish Editing').click();
expect.soft(await page.locator('.l-layout .l-layout__frame').count()).toEqual(1);
// Expand the Display Layout so we can remove the sine wave generator
await page.locator('.c-tree__item.is-navigated-object .c-disclosure-triangle').click();
// Bring up context menu and remove
await sineWaveGeneratorTreeItem.nth(1).click({ button: 'right' });
await page.locator('li[role="menuitem"]:has-text("Remove")').click();
await page.locator('button:has-text("OK")').click();
// delete
expect(await page.locator('.l-layout .l-layout__frame').count()).toEqual(0);
});
test('items in a display layout can be removed with object tree context menu when viewing another item', async ({
page
}) => {
test.info().annotations.push({
type: 'issue',
description: 'https://github.com/nasa/openmct/issues/3117'
});
// Create a Display Layout
const displayLayout = await createDomainObjectWithDefaults(page, {
type: 'Display Layout'
});
// Edit Display Layout
await page.locator('[title="Edit"]').click();
// Expand the 'My Items' folder in the left tree
await page.locator('.c-tree__item__view-control.c-disclosure-triangle').click();
// Add the Sine Wave Generator to the Display Layout and save changes
const treePane = page.getByRole('tree', {
name: 'Main Tree'
});
const sineWaveGeneratorTreeItem = treePane.getByRole('treeitem', {
name: new RegExp(sineWaveObject.name)
});
const layoutGridHolder = page.locator('.l-layout__grid-holder');
await sineWaveGeneratorTreeItem.dragTo(layoutGridHolder);
await page.locator('button[title="Save"]').click();
await page.locator('text=Save and Finish Editing').click();
expect.soft(await page.locator('.l-layout .l-layout__frame').count()).toEqual(1);
// Expand the Display Layout so we can remove the sine wave generator
await page.locator('.c-tree__item.is-navigated-object .c-disclosure-triangle').click();
// Go to the original Sine Wave Generator to navigate away from the Display Layout
await page.goto(sineWaveObject.url);
// Bring up context menu and remove
await sineWaveGeneratorTreeItem.first().click({ button: 'right' });
await page.locator('li[role="menuitem"]:has-text("Remove")').click();
await page.locator('button:has-text("OK")').click();
// navigate back to the display layout to confirm it has been removed
await page.goto(displayLayout.url);
expect(await page.locator('.l-layout .l-layout__frame').count()).toEqual(0);
});
}); });
/** /**
@ -200,18 +217,20 @@ test.describe('Display Layout', () => {
* @returns {Promise<string>} the formatted sin telemetry value * @returns {Promise<string>} the formatted sin telemetry value
*/ */
async function subscribeToTelemetry(page, objectIdentifier) { async function subscribeToTelemetry(page, objectIdentifier) {
const getTelemValuePromise = new Promise(resolve => page.exposeFunction('getTelemValue', resolve)); const getTelemValuePromise = new Promise((resolve) =>
page.exposeFunction('getTelemValue', resolve)
);
await page.evaluate(async (telemetryIdentifier) => { await page.evaluate(async (telemetryIdentifier) => {
const telemetryObject = await window.openmct.objects.get(telemetryIdentifier); const telemetryObject = await window.openmct.objects.get(telemetryIdentifier);
const metadata = window.openmct.telemetry.getMetadata(telemetryObject); const metadata = window.openmct.telemetry.getMetadata(telemetryObject);
const formats = await window.openmct.telemetry.getFormatMap(metadata); const formats = await window.openmct.telemetry.getFormatMap(metadata);
window.openmct.telemetry.subscribe(telemetryObject, (obj) => { window.openmct.telemetry.subscribe(telemetryObject, (obj) => {
const sinVal = obj.sin; const sinVal = obj.sin;
const formattedSinVal = formats.sin.format(sinVal); const formattedSinVal = formats.sin.format(sinVal);
window.getTelemValue(formattedSinVal); window.getTelemValue(formattedSinVal);
}); });
}, objectIdentifier); }, objectIdentifier);
return getTelemValuePromise; return getTelemValuePromise;
} }

View File

@ -25,216 +25,231 @@ const utils = require('../../../../helper/faultUtils');
const { selectInspectorTab } = require('../../../../appActions'); const { selectInspectorTab } = require('../../../../appActions');
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 }) => {
await utils.navigateToFaultManagementWithExample(page); await utils.navigateToFaultManagementWithExample(page);
}); });
test('Shows a criticality icon for every fault @unstable', async ({ page }) => { test('Shows a criticality icon for every fault @unstable', async ({ page }) => {
const faultCount = await page.locator('c-fault-mgmt__list').count(); const faultCount = await page.locator('c-fault-mgmt__list').count();
const criticalityIconCount = await page.locator('c-fault-mgmt__list-severity').count(); const criticalityIconCount = await page.locator('c-fault-mgmt__list-severity').count();
expect.soft(faultCount).toEqual(criticalityIconCount); expect.soft(faultCount).toEqual(criticalityIconCount);
}); });
test('When selecting a fault, it has an "is-selected" class and it\'s information shows in the inspector @unstable', async ({ page }) => { test('When selecting a fault, it has an "is-selected" class and it\'s information shows in the inspector @unstable', async ({
await utils.selectFaultItem(page, 1); page
}) => {
await utils.selectFaultItem(page, 1);
await selectInspectorTab(page, 'Fault Management Configuration'); await selectInspectorTab(page, 'Fault Management Configuration');
const selectedFaultName = await page.locator('.c-fault-mgmt__list.is-selected .c-fault-mgmt__list-faultname').textContent(); const selectedFaultName = await page
const inspectorFaultNameCount = await page.locator(`.c-inspector__properties >> :text("${selectedFaultName}")`).count(); .locator('.c-fault-mgmt__list.is-selected .c-fault-mgmt__list-faultname')
.textContent();
const inspectorFaultNameCount = await page
.locator(`.c-inspector__properties >> :text("${selectedFaultName}")`)
.count();
await expect.soft(page.locator('.c-faults-list-view-item-body > .c-fault-mgmt__list').first()).toHaveClass(/is-selected/); await expect
expect.soft(inspectorFaultNameCount).toEqual(1); .soft(page.locator('.c-faults-list-view-item-body > .c-fault-mgmt__list').first())
}); .toHaveClass(/is-selected/);
expect.soft(inspectorFaultNameCount).toEqual(1);
});
test('When selecting multiple faults, no specific fault information is shown in the inspector @unstable', async ({ page }) => { test('When selecting multiple faults, no specific fault information is shown in the inspector @unstable', async ({
await utils.selectFaultItem(page, 1); page
await utils.selectFaultItem(page, 2); }) => {
await utils.selectFaultItem(page, 1);
await utils.selectFaultItem(page, 2);
const selectedRows = page.locator('.c-fault-mgmt__list.is-selected .c-fault-mgmt__list-faultname'); const selectedRows = page.locator(
expect.soft(await selectedRows.count()).toEqual(2); '.c-fault-mgmt__list.is-selected .c-fault-mgmt__list-faultname'
);
expect.soft(await selectedRows.count()).toEqual(2);
await selectInspectorTab(page, 'Fault Management Configuration'); await selectInspectorTab(page, 'Fault Management Configuration');
const firstSelectedFaultName = await selectedRows.nth(0).textContent(); const firstSelectedFaultName = await selectedRows.nth(0).textContent();
const secondSelectedFaultName = await selectedRows.nth(1).textContent(); const secondSelectedFaultName = await selectedRows.nth(1).textContent();
const firstNameInInspectorCount = await page.locator(`.c-inspector__properties >> :text("${firstSelectedFaultName}")`).count(); const firstNameInInspectorCount = await page
const secondNameInInspectorCount = await page.locator(`.c-inspector__properties >> :text("${secondSelectedFaultName}")`).count(); .locator(`.c-inspector__properties >> :text("${firstSelectedFaultName}")`)
.count();
const secondNameInInspectorCount = await page
.locator(`.c-inspector__properties >> :text("${secondSelectedFaultName}")`)
.count();
expect.soft(firstNameInInspectorCount).toEqual(0); expect.soft(firstNameInInspectorCount).toEqual(0);
expect.soft(secondNameInInspectorCount).toEqual(0); expect.soft(secondNameInInspectorCount).toEqual(0);
}); });
test('Allows you to shelve a fault @unstable', async ({ page }) => { test('Allows you to shelve a fault @unstable', async ({ page }) => {
const shelvedFaultName = await utils.getFaultName(page, 2); const shelvedFaultName = await utils.getFaultName(page, 2);
const beforeShelvedFault = utils.getFaultByName(page, shelvedFaultName); const beforeShelvedFault = utils.getFaultByName(page, shelvedFaultName);
expect.soft(await beforeShelvedFault.count()).toBe(1); expect.soft(await beforeShelvedFault.count()).toBe(1);
await utils.shelveFault(page, 2); await utils.shelveFault(page, 2);
// check it is removed from standard view // check it is removed from standard view
const afterShelvedFault = utils.getFaultByName(page, shelvedFaultName); const afterShelvedFault = utils.getFaultByName(page, shelvedFaultName);
expect.soft(await afterShelvedFault.count()).toBe(0); expect.soft(await afterShelvedFault.count()).toBe(0);
await utils.changeViewTo(page, 'shelved'); await utils.changeViewTo(page, 'shelved');
const shelvedViewFault = utils.getFaultByName(page, shelvedFaultName); const shelvedViewFault = utils.getFaultByName(page, shelvedFaultName);
expect.soft(await shelvedViewFault.count()).toBe(1); expect.soft(await shelvedViewFault.count()).toBe(1);
}); });
test('Allows you to acknowledge a fault @unstable', async ({ page }) => { test('Allows you to acknowledge a fault @unstable', async ({ page }) => {
const acknowledgedFaultName = await utils.getFaultName(page, 3); const acknowledgedFaultName = await utils.getFaultName(page, 3);
await utils.acknowledgeFault(page, 3); await utils.acknowledgeFault(page, 3);
const fault = utils.getFault(page, 3); const fault = utils.getFault(page, 3);
await expect.soft(fault).toHaveClass(/is-acknowledged/); await expect.soft(fault).toHaveClass(/is-acknowledged/);
await utils.changeViewTo(page, 'acknowledged'); await utils.changeViewTo(page, 'acknowledged');
const acknowledgedViewFaultName = await utils.getFaultName(page, 1); const acknowledgedViewFaultName = await utils.getFaultName(page, 1);
expect.soft(acknowledgedFaultName).toEqual(acknowledgedViewFaultName); expect.soft(acknowledgedFaultName).toEqual(acknowledgedViewFaultName);
}); });
test('Allows you to shelve multiple faults @unstable', async ({ page }) => { test('Allows you to shelve multiple faults @unstable', async ({ page }) => {
const shelvedFaultNameOne = await utils.getFaultName(page, 1); const shelvedFaultNameOne = await utils.getFaultName(page, 1);
const shelvedFaultNameFour = await utils.getFaultName(page, 4); const shelvedFaultNameFour = await utils.getFaultName(page, 4);
const beforeShelvedFaultOne = utils.getFaultByName(page, shelvedFaultNameOne); const beforeShelvedFaultOne = utils.getFaultByName(page, shelvedFaultNameOne);
const beforeShelvedFaultFour = utils.getFaultByName(page, shelvedFaultNameFour); const beforeShelvedFaultFour = utils.getFaultByName(page, shelvedFaultNameFour);
expect.soft(await beforeShelvedFaultOne.count()).toBe(1); expect.soft(await beforeShelvedFaultOne.count()).toBe(1);
expect.soft(await beforeShelvedFaultFour.count()).toBe(1); expect.soft(await beforeShelvedFaultFour.count()).toBe(1);
await utils.shelveMultipleFaults(page, 1, 4); await utils.shelveMultipleFaults(page, 1, 4);
// check it is removed from standard view // check it is removed from standard view
const afterShelvedFaultOne = utils.getFaultByName(page, shelvedFaultNameOne); const afterShelvedFaultOne = utils.getFaultByName(page, shelvedFaultNameOne);
const afterShelvedFaultFour = utils.getFaultByName(page, shelvedFaultNameFour); const afterShelvedFaultFour = utils.getFaultByName(page, shelvedFaultNameFour);
expect.soft(await afterShelvedFaultOne.count()).toBe(0); expect.soft(await afterShelvedFaultOne.count()).toBe(0);
expect.soft(await afterShelvedFaultFour.count()).toBe(0); expect.soft(await afterShelvedFaultFour.count()).toBe(0);
await utils.changeViewTo(page, 'shelved'); await utils.changeViewTo(page, 'shelved');
const shelvedViewFaultOne = utils.getFaultByName(page, shelvedFaultNameOne); const shelvedViewFaultOne = utils.getFaultByName(page, shelvedFaultNameOne);
const shelvedViewFaultFour = utils.getFaultByName(page, shelvedFaultNameFour); const shelvedViewFaultFour = utils.getFaultByName(page, shelvedFaultNameFour);
expect.soft(await shelvedViewFaultOne.count()).toBe(1); expect.soft(await shelvedViewFaultOne.count()).toBe(1);
expect.soft(await shelvedViewFaultFour.count()).toBe(1); expect.soft(await shelvedViewFaultFour.count()).toBe(1);
}); });
test('Allows you to acknowledge multiple faults @unstable', async ({ page }) => { test('Allows you to acknowledge multiple faults @unstable', async ({ page }) => {
const acknowledgedFaultNameTwo = await utils.getFaultName(page, 2); const acknowledgedFaultNameTwo = await utils.getFaultName(page, 2);
const acknowledgedFaultNameFive = await utils.getFaultName(page, 5); const acknowledgedFaultNameFive = await utils.getFaultName(page, 5);
await utils.acknowledgeMultipleFaults(page, 2, 5); await utils.acknowledgeMultipleFaults(page, 2, 5);
const faultTwo = utils.getFault(page, 2); const faultTwo = utils.getFault(page, 2);
const faultFive = utils.getFault(page, 5); const faultFive = utils.getFault(page, 5);
// check they have been acknowledged // check they have been acknowledged
await expect.soft(faultTwo).toHaveClass(/is-acknowledged/); await expect.soft(faultTwo).toHaveClass(/is-acknowledged/);
await expect.soft(faultFive).toHaveClass(/is-acknowledged/); await expect.soft(faultFive).toHaveClass(/is-acknowledged/);
await utils.changeViewTo(page, 'acknowledged'); await utils.changeViewTo(page, 'acknowledged');
const acknowledgedViewFaultTwo = utils.getFaultByName(page, acknowledgedFaultNameTwo); const acknowledgedViewFaultTwo = utils.getFaultByName(page, acknowledgedFaultNameTwo);
const acknowledgedViewFaultFive = utils.getFaultByName(page, acknowledgedFaultNameFive); const acknowledgedViewFaultFive = utils.getFaultByName(page, acknowledgedFaultNameFive);
expect.soft(await acknowledgedViewFaultTwo.count()).toBe(1); expect.soft(await acknowledgedViewFaultTwo.count()).toBe(1);
expect.soft(await acknowledgedViewFaultFive.count()).toBe(1); expect.soft(await acknowledgedViewFaultFive.count()).toBe(1);
}); });
test('Allows you to search faults @unstable', async ({ page }) => { test('Allows you to search faults @unstable', async ({ page }) => {
const faultThreeNamespace = await utils.getFaultNamespace(page, 3); const faultThreeNamespace = await utils.getFaultNamespace(page, 3);
const faultTwoName = await utils.getFaultName(page, 2); const faultTwoName = await utils.getFaultName(page, 2);
const faultFiveTriggerTime = await utils.getFaultTriggerTime(page, 5); const faultFiveTriggerTime = await utils.getFaultTriggerTime(page, 5);
// should be all faults (5) // should be all faults (5)
let faultResultCount = await utils.getFaultResultCount(page); let faultResultCount = await utils.getFaultResultCount(page);
expect.soft(faultResultCount).toEqual(5); expect.soft(faultResultCount).toEqual(5);
// search namespace // search namespace
await utils.enterSearchTerm(page, faultThreeNamespace); await utils.enterSearchTerm(page, faultThreeNamespace);
faultResultCount = await utils.getFaultResultCount(page); faultResultCount = await utils.getFaultResultCount(page);
expect.soft(faultResultCount).toEqual(1); expect.soft(faultResultCount).toEqual(1);
expect.soft(await utils.getFaultNamespace(page, 1)).toEqual(faultThreeNamespace); expect.soft(await utils.getFaultNamespace(page, 1)).toEqual(faultThreeNamespace);
// all faults // all faults
await utils.clearSearch(page); await utils.clearSearch(page);
faultResultCount = await utils.getFaultResultCount(page); faultResultCount = await utils.getFaultResultCount(page);
expect.soft(faultResultCount).toEqual(5); expect.soft(faultResultCount).toEqual(5);
// search name // search name
await utils.enterSearchTerm(page, faultTwoName); await utils.enterSearchTerm(page, faultTwoName);
faultResultCount = await utils.getFaultResultCount(page); faultResultCount = await utils.getFaultResultCount(page);
expect.soft(faultResultCount).toEqual(1); expect.soft(faultResultCount).toEqual(1);
expect.soft(await utils.getFaultName(page, 1)).toEqual(faultTwoName); expect.soft(await utils.getFaultName(page, 1)).toEqual(faultTwoName);
// all faults // all faults
await utils.clearSearch(page); await utils.clearSearch(page);
faultResultCount = await utils.getFaultResultCount(page); faultResultCount = await utils.getFaultResultCount(page);
expect.soft(faultResultCount).toEqual(5); expect.soft(faultResultCount).toEqual(5);
// search triggerTime // search triggerTime
await utils.enterSearchTerm(page, faultFiveTriggerTime); await utils.enterSearchTerm(page, faultFiveTriggerTime);
faultResultCount = await utils.getFaultResultCount(page); faultResultCount = await utils.getFaultResultCount(page);
expect.soft(faultResultCount).toEqual(1); expect.soft(faultResultCount).toEqual(1);
expect.soft(await utils.getFaultTriggerTime(page, 1)).toEqual(faultFiveTriggerTime); expect.soft(await utils.getFaultTriggerTime(page, 1)).toEqual(faultFiveTriggerTime);
}); });
test('Allows you to sort faults @unstable', async ({ page }) => { test('Allows you to sort faults @unstable', async ({ page }) => {
const highestSeverity = await utils.getHighestSeverity(page); const highestSeverity = await utils.getHighestSeverity(page);
const lowestSeverity = await utils.getLowestSeverity(page); const lowestSeverity = await utils.getLowestSeverity(page);
const faultOneName = 'Example Fault 1'; const faultOneName = 'Example Fault 1';
const faultFiveName = 'Example Fault 5'; const faultFiveName = 'Example Fault 5';
let firstFaultName = await utils.getFaultName(page, 1); let firstFaultName = await utils.getFaultName(page, 1);
expect.soft(firstFaultName).toEqual(faultOneName); expect.soft(firstFaultName).toEqual(faultOneName);
await utils.sortFaultsBy(page, 'oldest-first'); await utils.sortFaultsBy(page, 'oldest-first');
firstFaultName = await utils.getFaultName(page, 1); firstFaultName = await utils.getFaultName(page, 1);
expect.soft(firstFaultName).toEqual(faultFiveName); expect.soft(firstFaultName).toEqual(faultFiveName);
await utils.sortFaultsBy(page, 'severity'); await utils.sortFaultsBy(page, 'severity');
const sortedHighestSeverity = await utils.getFaultSeverity(page, 1);
const sortedLowestSeverity = await utils.getFaultSeverity(page, 5);
expect.soft(sortedHighestSeverity).toEqual(highestSeverity);
expect.soft(sortedLowestSeverity).toEqual(lowestSeverity);
});
const sortedHighestSeverity = await utils.getFaultSeverity(page, 1);
const sortedLowestSeverity = await utils.getFaultSeverity(page, 5);
expect.soft(sortedHighestSeverity).toEqual(highestSeverity);
expect.soft(sortedLowestSeverity).toEqual(lowestSeverity);
});
}); });
test.describe('The Fault Management Plugin without using example faults', () => { test.describe('The Fault Management Plugin without using example faults', () => {
test.beforeEach(async ({ page }) => { test.beforeEach(async ({ page }) => {
await utils.navigateToFaultManagementWithoutExample(page); await utils.navigateToFaultManagementWithoutExample(page);
}); });
test('Shows no faults when no faults are provided @unstable', async ({ page }) => { test('Shows no faults when no faults are provided @unstable', async ({ page }) => {
const faultCount = await page.locator('c-fault-mgmt__list').count(); const faultCount = await page.locator('c-fault-mgmt__list').count();
expect.soft(faultCount).toEqual(0); expect.soft(faultCount).toEqual(0);
await utils.changeViewTo(page, 'acknowledged'); await utils.changeViewTo(page, 'acknowledged');
const acknowledgedCount = await page.locator('c-fault-mgmt__list').count(); const acknowledgedCount = await page.locator('c-fault-mgmt__list').count();
expect.soft(acknowledgedCount).toEqual(0); expect.soft(acknowledgedCount).toEqual(0);
await utils.changeViewTo(page, 'shelved'); await utils.changeViewTo(page, 'shelved');
const shelvedCount = await page.locator('c-fault-mgmt__list').count(); const shelvedCount = await page.locator('c-fault-mgmt__list').count();
expect.soft(shelvedCount).toEqual(0); expect.soft(shelvedCount).toEqual(0);
}); });
test('Will return no faults when searching @unstable', async ({ page }) => { test('Will return no faults when searching @unstable', async ({ page }) => {
await utils.enterSearchTerm(page, 'fault'); await utils.enterSearchTerm(page, 'fault');
const faultCount = await page.locator('c-fault-mgmt__list').count(); const faultCount = await page.locator('c-fault-mgmt__list').count();
expect.soft(faultCount).toEqual(0); expect.soft(faultCount).toEqual(0);
}); });
}); });

View File

@ -24,130 +24,138 @@ const { test, expect } = require('../../../../pluginFixtures');
const { createDomainObjectWithDefaults } = require('../../../../appActions'); const { createDomainObjectWithDefaults } = require('../../../../appActions');
test.describe('Flexible Layout', () => { test.describe('Flexible Layout', () => {
let sineWaveObject; let sineWaveObject;
let clockObject; let clockObject;
test.beforeEach(async ({ page }) => { test.beforeEach(async ({ page }) => {
await page.goto('./', { waitUntil: 'domcontentloaded' }); await page.goto('./', { waitUntil: 'domcontentloaded' });
// Create Sine Wave Generator // Create Sine Wave Generator
sineWaveObject = await createDomainObjectWithDefaults(page, { sineWaveObject = await createDomainObjectWithDefaults(page, {
type: 'Sine Wave Generator' type: 'Sine Wave Generator'
});
// Create Clock Object
clockObject = await createDomainObjectWithDefaults(page, {
type: 'Clock'
});
}); });
test('panes have the appropriate draggable attribute while in Edit and Browse modes', async ({ page }) => {
const treePane = page.getByRole('tree', {
name: 'Main Tree'
});
const sineWaveGeneratorTreeItem = treePane.getByRole('treeitem', {
name: new RegExp(sineWaveObject.name)
});
const clockTreeItem = treePane.getByRole('treeitem', {
name: new RegExp(clockObject.name)
});
// Create a Flexible Layout
await createDomainObjectWithDefaults(page, {
type: 'Flexible Layout'
});
// Edit Flexible Layout
await page.locator('[title="Edit"]').click();
// Expand the 'My Items' folder in the left tree // Create Clock Object
await page.locator('.c-tree__item__view-control.c-disclosure-triangle').first().click(); clockObject = await createDomainObjectWithDefaults(page, {
// Add the Sine Wave Generator and Clock to the Flexible Layout type: 'Clock'
await sineWaveGeneratorTreeItem.dragTo(page.locator('.c-fl__container.is-empty').first());
await clockTreeItem.dragTo(page.locator('.c-fl__container.is-empty'));
// Check that panes can be dragged while Flexible Layout is in Edit mode
let dragWrapper = page.locator('.c-fl-container__frames-holder .c-fl-frame__drag-wrapper').first();
await expect(dragWrapper).toHaveAttribute('draggable', 'true');
// Save Flexible Layout
await page.locator('button[title="Save"]').click();
await page.locator('text=Save and Finish Editing').click();
// Check that panes are not draggable while Flexible Layout is in Browse mode
dragWrapper = page.locator('.c-fl-container__frames-holder .c-fl-frame__drag-wrapper').first();
await expect(dragWrapper).toHaveAttribute('draggable', 'false');
}); });
test('items in a flexible layout can be removed with object tree context menu when viewing the flexible layout', async ({ page }) => { });
const treePane = page.getByRole('tree', { test('panes have the appropriate draggable attribute while in Edit and Browse modes', async ({
name: 'Main Tree' page
}); }) => {
const sineWaveGeneratorTreeItem = treePane.getByRole('treeitem', { const treePane = page.getByRole('tree', {
name: new RegExp(sineWaveObject.name) name: 'Main Tree'
});
// Create a Display Layout
await createDomainObjectWithDefaults(page, {
type: 'Flexible Layout'
});
// Edit Flexible Layout
await page.locator('[title="Edit"]').click();
// Expand the 'My Items' folder in the left tree
await page.locator('.c-tree__item__view-control.c-disclosure-triangle').first().click();
// Add the Sine Wave Generator to the Flexible Layout and save changes
await sineWaveGeneratorTreeItem.dragTo(page.locator('.c-fl__container.is-empty').first());
await page.locator('button[title="Save"]').click();
await page.locator('text=Save and Finish Editing').click();
expect.soft(await page.locator('.c-fl-container__frame').count()).toEqual(1);
// Expand the Flexible Layout so we can remove the sine wave generator
await page.locator('.c-tree__item.is-navigated-object .c-disclosure-triangle').click();
// Bring up context menu and remove
await sineWaveGeneratorTreeItem.first().click({ button: 'right' });
await page.locator('li[role="menuitem"]:has-text("Remove")').click();
await page.locator('button:has-text("OK")').click();
// Verify that the item has been removed from the layout
expect(await page.locator('.c-fl-container__frame').count()).toEqual(0);
}); });
test('items in a flexible layout can be removed with object tree context menu when viewing another item', async ({ page }) => { const sineWaveGeneratorTreeItem = treePane.getByRole('treeitem', {
test.info().annotations.push({ name: new RegExp(sineWaveObject.name)
type: 'issue',
description: 'https://github.com/nasa/openmct/issues/3117'
});
const treePane = page.getByRole('tree', {
name: 'Main Tree'
});
const sineWaveGeneratorTreeItem = treePane.getByRole('treeitem', {
name: new RegExp(sineWaveObject.name)
});
// Create a Flexible Layout
const flexibleLayout = await createDomainObjectWithDefaults(page, {
type: 'Flexible Layout'
});
// Edit Flexible Layout
await page.locator('[title="Edit"]').click();
// Expand the 'My Items' folder in the left tree
await page.locator('.c-tree__item__view-control.c-disclosure-triangle').click();
// Add the Sine Wave Generator to the Flexible Layout and save changes
await sineWaveGeneratorTreeItem.dragTo(page.locator('.c-fl__container.is-empty').first());
await page.locator('button[title="Save"]').click();
await page.locator('text=Save and Finish Editing').click();
expect.soft(await page.locator('.c-fl-container__frame').count()).toEqual(1);
// Expand the Flexible Layout so we can remove the sine wave generator
await page.locator('.c-tree__item.is-navigated-object .c-disclosure-triangle').click();
// Go to the original Sine Wave Generator to navigate away from the Flexible Layout
await page.goto(sineWaveObject.url);
// Bring up context menu and remove
await sineWaveGeneratorTreeItem.first().click({ button: 'right' });
await page.locator('li[role="menuitem"]:has-text("Remove")').click();
await page.locator('button:has-text("OK")').click();
// navigate back to the display layout to confirm it has been removed
await page.goto(flexibleLayout.url);
// Verify that the item has been removed from the layout
expect(await page.locator('.c-fl-container__frame').count()).toEqual(0);
}); });
const clockTreeItem = treePane.getByRole('treeitem', {
name: new RegExp(clockObject.name)
});
// Create a Flexible Layout
await createDomainObjectWithDefaults(page, {
type: 'Flexible Layout'
});
// Edit Flexible Layout
await page.locator('[title="Edit"]').click();
// Expand the 'My Items' folder in the left tree
await page.locator('.c-tree__item__view-control.c-disclosure-triangle').first().click();
// Add the Sine Wave Generator and Clock to the Flexible Layout
await sineWaveGeneratorTreeItem.dragTo(page.locator('.c-fl__container.is-empty').first());
await clockTreeItem.dragTo(page.locator('.c-fl__container.is-empty'));
// Check that panes can be dragged while Flexible Layout is in Edit mode
let dragWrapper = page
.locator('.c-fl-container__frames-holder .c-fl-frame__drag-wrapper')
.first();
await expect(dragWrapper).toHaveAttribute('draggable', 'true');
// Save Flexible Layout
await page.locator('button[title="Save"]').click();
await page.locator('text=Save and Finish Editing').click();
// Check that panes are not draggable while Flexible Layout is in Browse mode
dragWrapper = page.locator('.c-fl-container__frames-holder .c-fl-frame__drag-wrapper').first();
await expect(dragWrapper).toHaveAttribute('draggable', 'false');
});
test('items in a flexible layout can be removed with object tree context menu when viewing the flexible layout', async ({
page
}) => {
const treePane = page.getByRole('tree', {
name: 'Main Tree'
});
const sineWaveGeneratorTreeItem = treePane.getByRole('treeitem', {
name: new RegExp(sineWaveObject.name)
});
// Create a Display Layout
await createDomainObjectWithDefaults(page, {
type: 'Flexible Layout'
});
// Edit Flexible Layout
await page.locator('[title="Edit"]').click();
// Expand the 'My Items' folder in the left tree
await page.locator('.c-tree__item__view-control.c-disclosure-triangle').first().click();
// Add the Sine Wave Generator to the Flexible Layout and save changes
await sineWaveGeneratorTreeItem.dragTo(page.locator('.c-fl__container.is-empty').first());
await page.locator('button[title="Save"]').click();
await page.locator('text=Save and Finish Editing').click();
expect.soft(await page.locator('.c-fl-container__frame').count()).toEqual(1);
// Expand the Flexible Layout so we can remove the sine wave generator
await page.locator('.c-tree__item.is-navigated-object .c-disclosure-triangle').click();
// Bring up context menu and remove
await sineWaveGeneratorTreeItem.first().click({ button: 'right' });
await page.locator('li[role="menuitem"]:has-text("Remove")').click();
await page.locator('button:has-text("OK")').click();
// Verify that the item has been removed from the layout
expect(await page.locator('.c-fl-container__frame').count()).toEqual(0);
});
test('items in a flexible layout can be removed with object tree context menu when viewing another item', async ({
page
}) => {
test.info().annotations.push({
type: 'issue',
description: 'https://github.com/nasa/openmct/issues/3117'
});
const treePane = page.getByRole('tree', {
name: 'Main Tree'
});
const sineWaveGeneratorTreeItem = treePane.getByRole('treeitem', {
name: new RegExp(sineWaveObject.name)
});
// Create a Flexible Layout
const flexibleLayout = await createDomainObjectWithDefaults(page, {
type: 'Flexible Layout'
});
// Edit Flexible Layout
await page.locator('[title="Edit"]').click();
// Expand the 'My Items' folder in the left tree
await page.locator('.c-tree__item__view-control.c-disclosure-triangle').click();
// Add the Sine Wave Generator to the Flexible Layout and save changes
await sineWaveGeneratorTreeItem.dragTo(page.locator('.c-fl__container.is-empty').first());
await page.locator('button[title="Save"]').click();
await page.locator('text=Save and Finish Editing').click();
expect.soft(await page.locator('.c-fl-container__frame').count()).toEqual(1);
// Expand the Flexible Layout so we can remove the sine wave generator
await page.locator('.c-tree__item.is-navigated-object .c-disclosure-triangle').click();
// Go to the original Sine Wave Generator to navigate away from the Flexible Layout
await page.goto(sineWaveObject.url);
// Bring up context menu and remove
await sineWaveGeneratorTreeItem.first().click({ button: 'right' });
await page.locator('li[role="menuitem"]:has-text("Remove")').click();
await page.locator('button:has-text("OK")').click();
// navigate back to the display layout to confirm it has been removed
await page.goto(flexibleLayout.url);
// Verify that the item has been removed from the layout
expect(await page.locator('.c-fl-container__frame').count()).toEqual(0);
});
}); });

View File

@ -21,104 +21,116 @@
*****************************************************************************/ *****************************************************************************/
/* /*
* 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'); const { test, expect } = require('../../../../pluginFixtures');
const { createDomainObjectWithDefaults } = require('../../../../appActions'); const { createDomainObjectWithDefaults } = require('../../../../appActions');
const uuid = require('uuid').v4; const uuid = require('uuid').v4;
test.describe('Gauge', () => { test.describe('Gauge', () => {
test.beforeEach(async ({ page }) => { test.beforeEach(async ({ page }) => {
// Open a browser, navigate to the main page, and wait until all networkevents to resolve // Open a browser, navigate to the main page, and wait until all networkevents to resolve
await page.goto('./', { waitUntil: 'domcontentloaded' }); await page.goto('./', { waitUntil: 'domcontentloaded' });
});
test('Can add and remove telemetry sources @unstable', async ({ page }) => {
// Create the gauge with defaults
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
const swg1 = await createDomainObjectWithDefaults(page, {
type: 'Sine Wave Generator',
name: `swg-${uuid()}`,
parent: gauge.uuid
}); });
test('Can add and remove telemetry sources @unstable', async ({ page }) => { // Navigate to the gauge and verify that
// Create the gauge with defaults // the SWG appears in the elements pool
const gauge = await createDomainObjectWithDefaults(page, { type: 'Gauge' }); await page.goto(gauge.url);
const editButtonLocator = page.locator('button[title="Edit"]'); await editButtonLocator.click();
const saveButtonLocator = page.locator('button[title="Save"]'); await expect.soft(page.locator(`#inspector-elements-tree >> text=${swg1.name}`)).toBeVisible();
await saveButtonLocator.click();
await page.locator('li[title="Save and Finish Editing"]').click();
// Create a sine wave generator within the gauge // Create another sine wave generator within the gauge
const swg1 = await createDomainObjectWithDefaults(page, { const swg2 = await createDomainObjectWithDefaults(page, {
type: 'Sine Wave Generator', type: 'Sine Wave Generator',
name: `swg-${uuid()}`, name: `swg-${uuid()}`,
parent: gauge.uuid parent: gauge.uuid
});
// Navigate to the gauge and verify that
// the SWG appears in the elements pool
await page.goto(gauge.url);
await editButtonLocator.click();
await expect.soft(page.locator(`#inspector-elements-tree >> text=${swg1.name}`)).toBeVisible();
await saveButtonLocator.click();
await page.locator('li[title="Save and Finish Editing"]').click();
// Create another sine wave generator within the gauge
const swg2 = await createDomainObjectWithDefaults(page, {
type: 'Sine Wave Generator',
name: `swg-${uuid()}`,
parent: gauge.uuid
});
// Verify that the 'Replace telemetry source' modal appears and accept it
await expect.soft(page.locator('text=This action will replace the current telemetry source. Do you want to continue?')).toBeVisible();
await page.click('text=Ok');
// Navigate to the gauge and verify that the new SWG
// appears in the elements pool and the old one is gone
await page.goto(gauge.url);
await editButtonLocator.click();
await expect.soft(page.locator(`#inspector-elements-tree >> text=${swg1.name}`)).toBeHidden();
await expect.soft(page.locator(`#inspector-elements-tree >> text=${swg2.name}`)).toBeVisible();
await saveButtonLocator.click();
// Right click on the new SWG in the elements pool and delete it
await page.locator(`#inspector-elements-tree >> text=${swg2.name}`).click({
button: 'right'
});
await page.locator('li[title="Remove this object from its containing object."]').click();
// Verify that the 'Remove object' confirmation modal appears and accept it
await expect.soft(page.locator('text=Warning! This action will remove this object. Are you sure you want to continue?')).toBeVisible();
await page.click('text=Ok');
// Verify that the elements pool shows no elements
await expect(page.locator('text="No contained elements"')).toBeVisible();
}); });
test('Can create a non-default Gauge', async ({ page }) => {
test.info().annotations.push({
type: 'issue',
description: 'https://github.com/nasa/openmct/issues/5356'
});
//Click the Create button
await page.click('button:has-text("Create")');
// Click the object specified by 'type' // Verify that the 'Replace telemetry source' modal appears and accept it
await page.click(`li[role='menuitem']:text("Gauge")`); await expect
// FIXME: We need better selectors for these custom form controls .soft(
const displayCurrentValueSwitch = page.locator('.c-toggle-switch__slider >> nth=0'); page.locator(
await displayCurrentValueSwitch.setChecked(false); 'text=This action will replace the current telemetry source. Do you want to continue?'
await page.click('button[aria-label="Save"]'); )
)
.toBeVisible();
await page.click('text=Ok');
// TODO: Verify changes in the UI // Navigate to the gauge and verify that the new SWG
// appears in the elements pool and the old one is gone
await page.goto(gauge.url);
await editButtonLocator.click();
await expect.soft(page.locator(`#inspector-elements-tree >> text=${swg1.name}`)).toBeHidden();
await expect.soft(page.locator(`#inspector-elements-tree >> text=${swg2.name}`)).toBeVisible();
await saveButtonLocator.click();
// Right click on the new SWG in the elements pool and delete it
await page.locator(`#inspector-elements-tree >> text=${swg2.name}`).click({
button: 'right'
}); });
test('Can edit a single Gauge-specific property', async ({ page }) => { await page.locator('li[title="Remove this object from its containing object."]').click();
test.info().annotations.push({
type: 'issue',
description: 'https://github.com/nasa/openmct/issues/5985'
});
// Create the gauge with defaults // Verify that the 'Remove object' confirmation modal appears and accept it
await createDomainObjectWithDefaults(page, { type: 'Gauge' }); await expect
await page.click('button[title="More options"]'); .soft(
await page.click('li[role="menuitem"]:has-text("Edit Properties")'); page.locator(
// FIXME: We need better selectors for these custom form controls 'text=Warning! This action will remove this object. Are you sure you want to continue?'
const displayCurrentValueSwitch = page.locator('.c-toggle-switch__slider >> nth=0'); )
await displayCurrentValueSwitch.setChecked(false); )
await page.click('button[aria-label="Save"]'); .toBeVisible();
await page.click('text=Ok');
// TODO: Verify changes in the UI // Verify that the elements pool shows no elements
await expect(page.locator('text="No contained elements"')).toBeVisible();
});
test('Can create a non-default Gauge', async ({ page }) => {
test.info().annotations.push({
type: 'issue',
description: 'https://github.com/nasa/openmct/issues/5356'
}); });
//Click the Create button
await page.click('button:has-text("Create")');
// Click the object specified by 'type'
await page.click(`li[role='menuitem']:text("Gauge")`);
// FIXME: We need better selectors for these custom form controls
const displayCurrentValueSwitch = page.locator('.c-toggle-switch__slider >> nth=0');
await displayCurrentValueSwitch.setChecked(false);
await page.click('button[aria-label="Save"]');
// TODO: Verify changes in the UI
});
test('Can edit a single Gauge-specific property', async ({ page }) => {
test.info().annotations.push({
type: 'issue',
description: 'https://github.com/nasa/openmct/issues/5985'
});
// Create the gauge with defaults
await createDomainObjectWithDefaults(page, { type: 'Gauge' });
await page.click('button[title="More options"]');
await page.click('li[role="menuitem"]:has-text("Edit Properties")');
// FIXME: We need better selectors for these custom form controls
const displayCurrentValueSwitch = page.locator('.c-toggle-switch__slider >> nth=0');
await displayCurrentValueSwitch.setChecked(false);
await page.click('button[aria-label="Save"]');
// TODO: Verify changes in the UI
});
}); });

File diff suppressed because it is too large Load Diff

View File

@ -29,22 +29,31 @@ This test suite is dedicated to tests which verify the basic operations surround
const { test, expect } = require('../../../../baseFixtures'); const { test, expect } = require('../../../../baseFixtures');
test.describe('ExportAsJSON', () => { test.describe('ExportAsJSON', () => {
test.fixme('Create a basic object and verify that it can be exported as JSON from Tree', async ({ page }) => { test.fixme(
//Create domain object 'Create a basic object and verify that it can be exported as JSON from Tree',
//Save Domain Object async ({ page }) => {
//Verify that the newly created domain object can be exported as JSON from the Tree //Create domain object
}); //Save Domain Object
test.fixme('Create a basic object and verify that it can be exported as JSON from 3 dot menu', async ({ page }) => { //Verify that the newly created domain object can be exported as JSON from the Tree
//Create domain object }
//Save Domain Object );
//Verify that the newly created domain object can be exported as JSON from the 3 dot menu test.fixme(
}); 'Create a basic object and verify that it can be exported as JSON from 3 dot menu',
test.fixme('Verify that a nested Object can be exported as JSON', async ({ page }) => { async ({ page }) => {
// Create 2 objects with hierarchy //Create domain object
// Export as JSON //Save Domain Object
// Verify Hiearchy //Verify that the newly created domain object can be exported as JSON from the 3 dot menu
}); }
test.fixme('Verify that the ExportAsJSON dropdown does not appear for the item X', async ({ page }) => { );
// Other than non-persistible objects test.fixme('Verify that a nested Object can be exported as JSON', async ({ page }) => {
}); // Create 2 objects with hierarchy
// Export as JSON
// Verify Hiearchy
});
test.fixme(
'Verify that the ExportAsJSON dropdown does not appear for the item X',
async ({ page }) => {
// Other than non-persistible objects
}
);
}); });

View File

@ -29,20 +29,26 @@ This test suite is dedicated to tests which verify the basic operations surround
const { test, expect } = require('../../../../baseFixtures'); const { test, expect } = require('../../../../baseFixtures');
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 }) => {
//Verify that an testdata JSON file can be imported from Tree //Verify that an testdata JSON file can be imported from Tree
//Verify correctness of imported domain object //Verify correctness of imported domain object
}); });
test.fixme('Verify that domain object can be importAsJSON from 3 dot menu on folder', async ({ page }) => { test.fixme(
//Verify that an testdata JSON file can be imported from 3 dot menu on folder domain object 'Verify that domain object can be importAsJSON from 3 dot menu on folder',
//Verify correctness of imported domain object async ({ page }) => {
}); //Verify that an testdata JSON file can be imported from 3 dot menu on folder domain object
test.fixme('Verify that a nested Objects can be importAsJSON', async ({ page }) => { //Verify correctness of imported domain object
// Testdata with hierarchy }
// ImportAsJSON on Tree );
// Verify Hierarchy test.fixme('Verify that a nested Objects can be importAsJSON', async ({ page }) => {
}); // Testdata with hierarchy
test.fixme('Verify that the ImportAsJSON dropdown does not appear for the item X', async ({ page }) => { // ImportAsJSON on Tree
// Other than non-persistible objects // Verify Hierarchy
}); });
test.fixme(
'Verify that the ImportAsJSON dropdown does not appear for the item X',
async ({ page }) => {
// Other than non-persistible objects
}
);
}); });

View File

@ -21,189 +21,201 @@
*****************************************************************************/ *****************************************************************************/
const { test, expect } = require('../../../../pluginFixtures'); const { test, expect } = require('../../../../pluginFixtures');
const { createDomainObjectWithDefaults, setStartOffset, setFixedTimeMode, setRealTimeMode, selectInspectorTab } = require('../../../../appActions'); const {
createDomainObjectWithDefaults,
setStartOffset,
setFixedTimeMode,
setRealTimeMode,
selectInspectorTab
} = require('../../../../appActions');
test.describe('Testing LAD table configuration', () => { test.describe('Testing LAD table configuration', () => {
test.beforeEach(async ({ page }) => { test.beforeEach(async ({ page }) => {
await page.goto('./', { waitUntil: 'domcontentloaded' }); await page.goto('./', { waitUntil: 'domcontentloaded' });
// Create LAD table // Create LAD table
const ladTable = await createDomainObjectWithDefaults(page, { const ladTable = await createDomainObjectWithDefaults(page, {
type: 'LAD Table', type: 'LAD Table',
name: "Test LAD Table" name: 'Test LAD Table'
});
// Create Sine Wave Generator
await createDomainObjectWithDefaults(page, {
type: 'Sine Wave Generator',
name: "Test Sine Wave Generator",
parent: ladTable.uuid
});
await page.goto(ladTable.url);
});
test('in edit mode, LAD Tables provide ability to hide columns', async ({ page }) => {
// Edit LAD table
await page.locator('[title="Edit"]').click();
// // Expand the 'My Items' folder in the left tree
// await page.locator('.c-tree__item__view-control.c-disclosure-triangle').click();
// // Add the Sine Wave Generator to the LAD table and save changes
// await page.dragAndDrop('role=treeitem[name=/Test Sine Wave Generator/]', '.c-lad-table-wrapper');
// select configuration tab in inspector
await selectInspectorTab(page, 'LAD Table Configuration');
// make sure headers are visible initially
await expect(page.getByRole('cell', { name: 'Timestamp' })).toBeVisible();
await expect(page.getByRole('cell', { name: 'Units' })).toBeVisible();
await expect(page.getByRole('cell', { name: 'Type' })).toBeVisible();
// hide timestamp column
await page.getByLabel('Timestamp').uncheck();
await expect(page.getByRole('cell', { name: 'Timestamp' })).toBeHidden();
await expect(page.getByRole('cell', { name: 'Units' })).toBeVisible();
await expect(page.getByRole('cell', { name: 'Type' })).toBeVisible();
// hide units & type column
await page.getByLabel('Units').uncheck();
await page.getByLabel('Type').uncheck();
await expect(page.getByRole('cell', { name: 'Timestamp' })).toBeHidden();
await expect(page.getByRole('cell', { name: 'Units' })).toBeHidden();
await expect(page.getByRole('cell', { name: 'Type' })).toBeHidden();
// save and reload and verify they columns are still hidden
await page.locator('button[title="Save"]').click();
await page.locator('text=Save and Finish Editing').click();
await page.reload();
await expect(page.getByRole('cell', { name: 'Timestamp' })).toBeHidden();
await expect(page.getByRole('cell', { name: 'Units' })).toBeHidden();
await expect(page.getByRole('cell', { name: 'Type' })).toBeHidden();
// Edit LAD table
await page.locator('[title="Edit"]').click();
await selectInspectorTab(page, 'LAD Table Configuration');
// show timestamp column
await page.getByLabel('Timestamp').check();
await expect(page.getByRole('cell', { name: 'Units' })).toBeHidden();
await expect(page.getByRole('cell', { name: 'Type' })).toBeHidden();
await expect(page.getByRole('cell', { name: 'Timestamp' })).toBeVisible();
// save and reload and make sure only timestamp is still visible
await page.locator('button[title="Save"]').click();
await page.locator('text=Save and Finish Editing').click();
await page.reload();
await expect(page.getByRole('cell', { name: 'Units' })).toBeHidden();
await expect(page.getByRole('cell', { name: 'Type' })).toBeHidden();
await expect(page.getByRole('cell', { name: 'Timestamp' })).toBeVisible();
// Edit LAD table
await page.locator('[title="Edit"]').click();
await selectInspectorTab(page, 'LAD Table Configuration');
// show units and type columns
await page.getByLabel('Units').check();
await page.getByLabel('Type').check();
await expect(page.getByRole('cell', { name: 'Timestamp' })).toBeVisible();
await expect(page.getByRole('cell', { name: 'Units' })).toBeVisible();
await expect(page.getByRole('cell', { name: 'Type' })).toBeVisible();
// save and reload and make sure all columns are still visible
await page.locator('button[title="Save"]').click();
await page.locator('text=Save and Finish Editing').click();
await page.reload();
await expect(page.getByRole('cell', { name: 'Timestamp' })).toBeVisible();
await expect(page.getByRole('cell', { name: 'Units' })).toBeVisible();
await expect(page.getByRole('cell', { name: 'Type' })).toBeVisible();
}); });
test('LAD Tables don\'t allow selection of rows but does show context click menus', async ({ page }) => { // Create Sine Wave Generator
const cell = await page.locator('.js-first-data'); await createDomainObjectWithDefaults(page, {
const userSelectable = await cell.evaluate((el) => { type: 'Sine Wave Generator',
return window.getComputedStyle(el).getPropertyValue('user-select'); name: 'Test Sine Wave Generator',
}); parent: ladTable.uuid
expect(userSelectable).toBe('none');
// Right-click on the LAD table row
await cell.click({
button: 'right'
});
const menuOptions = page.locator('.c-menu ul');
await expect.soft(menuOptions).toContainText('View Full Datum');
await expect.soft(menuOptions).toContainText('View Historical Data');
await expect.soft(menuOptions).toContainText('Remove');
// await page.locator('li[title="Remove this object from its containing object."]').click();
}); });
await page.goto(ladTable.url);
});
test('in edit mode, LAD Tables provide ability to hide columns', async ({ page }) => {
// Edit LAD table
await page.locator('[title="Edit"]').click();
// // Expand the 'My Items' folder in the left tree
// await page.locator('.c-tree__item__view-control.c-disclosure-triangle').click();
// // Add the Sine Wave Generator to the LAD table and save changes
// await page.dragAndDrop('role=treeitem[name=/Test Sine Wave Generator/]', '.c-lad-table-wrapper');
// select configuration tab in inspector
await selectInspectorTab(page, 'LAD Table Configuration');
// make sure headers are visible initially
await expect(page.getByRole('cell', { name: 'Timestamp' })).toBeVisible();
await expect(page.getByRole('cell', { name: 'Units' })).toBeVisible();
await expect(page.getByRole('cell', { name: 'Type' })).toBeVisible();
// hide timestamp column
await page.getByLabel('Timestamp').uncheck();
await expect(page.getByRole('cell', { name: 'Timestamp' })).toBeHidden();
await expect(page.getByRole('cell', { name: 'Units' })).toBeVisible();
await expect(page.getByRole('cell', { name: 'Type' })).toBeVisible();
// hide units & type column
await page.getByLabel('Units').uncheck();
await page.getByLabel('Type').uncheck();
await expect(page.getByRole('cell', { name: 'Timestamp' })).toBeHidden();
await expect(page.getByRole('cell', { name: 'Units' })).toBeHidden();
await expect(page.getByRole('cell', { name: 'Type' })).toBeHidden();
// save and reload and verify they columns are still hidden
await page.locator('button[title="Save"]').click();
await page.locator('text=Save and Finish Editing').click();
await page.reload();
await expect(page.getByRole('cell', { name: 'Timestamp' })).toBeHidden();
await expect(page.getByRole('cell', { name: 'Units' })).toBeHidden();
await expect(page.getByRole('cell', { name: 'Type' })).toBeHidden();
// Edit LAD table
await page.locator('[title="Edit"]').click();
await selectInspectorTab(page, 'LAD Table Configuration');
// show timestamp column
await page.getByLabel('Timestamp').check();
await expect(page.getByRole('cell', { name: 'Units' })).toBeHidden();
await expect(page.getByRole('cell', { name: 'Type' })).toBeHidden();
await expect(page.getByRole('cell', { name: 'Timestamp' })).toBeVisible();
// save and reload and make sure only timestamp is still visible
await page.locator('button[title="Save"]').click();
await page.locator('text=Save and Finish Editing').click();
await page.reload();
await expect(page.getByRole('cell', { name: 'Units' })).toBeHidden();
await expect(page.getByRole('cell', { name: 'Type' })).toBeHidden();
await expect(page.getByRole('cell', { name: 'Timestamp' })).toBeVisible();
// Edit LAD table
await page.locator('[title="Edit"]').click();
await selectInspectorTab(page, 'LAD Table Configuration');
// show units and type columns
await page.getByLabel('Units').check();
await page.getByLabel('Type').check();
await expect(page.getByRole('cell', { name: 'Timestamp' })).toBeVisible();
await expect(page.getByRole('cell', { name: 'Units' })).toBeVisible();
await expect(page.getByRole('cell', { name: 'Type' })).toBeVisible();
// save and reload and make sure all columns are still visible
await page.locator('button[title="Save"]').click();
await page.locator('text=Save and Finish Editing').click();
await page.reload();
await expect(page.getByRole('cell', { name: 'Timestamp' })).toBeVisible();
await expect(page.getByRole('cell', { name: 'Units' })).toBeVisible();
await expect(page.getByRole('cell', { name: 'Type' })).toBeVisible();
});
test("LAD Tables don't allow selection of rows but does show context click menus", async ({
page
}) => {
const cell = await page.locator('.js-first-data');
const userSelectable = await cell.evaluate((el) => {
return window.getComputedStyle(el).getPropertyValue('user-select');
});
expect(userSelectable).toBe('none');
// Right-click on the LAD table row
await cell.click({
button: 'right'
});
const menuOptions = page.locator('.c-menu ul');
await expect.soft(menuOptions).toContainText('View Full Datum');
await expect.soft(menuOptions).toContainText('View Historical Data');
await expect.soft(menuOptions).toContainText('Remove');
// await page.locator('li[title="Remove this object from its containing object."]').click();
});
}); });
test.describe('Testing LAD table @unstable', () => { test.describe('Testing LAD table @unstable', () => {
let sineWaveObject; let sineWaveObject;
test.beforeEach(async ({ page }) => { test.beforeEach(async ({ page }) => {
await page.goto('./', { waitUntil: 'domcontentloaded' }); await page.goto('./', { waitUntil: 'domcontentloaded' });
await setRealTimeMode(page); await setRealTimeMode(page);
// Create Sine Wave Generator // Create Sine Wave Generator
sineWaveObject = await createDomainObjectWithDefaults(page, { sineWaveObject = await createDomainObjectWithDefaults(page, {
type: 'Sine Wave Generator', type: 'Sine Wave Generator',
name: "Test Sine Wave Generator" name: 'Test Sine Wave Generator'
});
}); });
test('telemetry value exactly matches latest telemetry value received in real time', async ({ page }) => { });
// Create LAD table test('telemetry value exactly matches latest telemetry value received in real time', async ({
await createDomainObjectWithDefaults(page, { page
type: 'LAD Table', }) => {
name: "Test LAD Table" // Create LAD table
}); await createDomainObjectWithDefaults(page, {
// Edit LAD table type: 'LAD Table',
await page.locator('[title="Edit"]').click(); name: 'Test LAD Table'
// Expand the 'My Items' folder in the left tree
await page.locator('.c-tree__item__view-control.c-disclosure-triangle').click();
// Add the Sine Wave Generator to the LAD table and save changes
await page.dragAndDrop('text=Test Sine Wave Generator', '.c-lad-table-wrapper');
await page.locator('button[title="Save"]').click();
await page.locator('text=Save and Finish Editing').click();
// Subscribe to the Sine Wave Generator data
// On getting data, check if the value found in the LAD table is the most recent value
// from the Sine Wave Generator
const getTelemValuePromise = await subscribeToTelemetry(page, sineWaveObject.uuid);
const subscribeTelemValue = await getTelemValuePromise;
const ladTableValuePromise = await page.waitForSelector(`text="${subscribeTelemValue}"`);
const ladTableValue = await ladTableValuePromise.textContent();
expect(ladTableValue).toBe(subscribeTelemValue);
}); });
test('telemetry value exactly matches latest telemetry value received in fixed time', async ({ page }) => { // Edit LAD table
// Create LAD table await page.locator('[title="Edit"]').click();
await createDomainObjectWithDefaults(page, {
type: 'LAD Table',
name: "Test LAD Table"
});
// Edit LAD table
await page.locator('[title="Edit"]').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();
// Add the Sine Wave Generator to the LAD table and save changes // Add the Sine Wave Generator to the LAD table and save changes
await page.dragAndDrop('text=Test Sine Wave Generator', '.c-lad-table-wrapper'); await page.dragAndDrop('text=Test Sine Wave Generator', '.c-lad-table-wrapper');
await page.locator('button[title="Save"]').click(); await page.locator('button[title="Save"]').click();
await page.locator('text=Save and Finish Editing').click(); await page.locator('text=Save and Finish Editing').click();
// Subscribe to the Sine Wave Generator data // Subscribe to the Sine Wave Generator data
const getTelemValuePromise = await subscribeToTelemetry(page, sineWaveObject.uuid); // On getting data, check if the value found in the LAD table is the most recent value
// Set an offset of 1 minute and then change the time mode to fixed to set a 1 minute historical window // from the Sine Wave Generator
await setStartOffset(page, { mins: '1' }); const getTelemValuePromise = await subscribeToTelemetry(page, sineWaveObject.uuid);
await setFixedTimeMode(page); const subscribeTelemValue = await getTelemValuePromise;
const ladTableValuePromise = await page.waitForSelector(`text="${subscribeTelemValue}"`);
const ladTableValue = await ladTableValuePromise.textContent();
// On getting data, check if the value found in the LAD table is the most recent value expect(ladTableValue).toBe(subscribeTelemValue);
// from the Sine Wave Generator });
const subscribeTelemValue = await getTelemValuePromise; test('telemetry value exactly matches latest telemetry value received in fixed time', async ({
const ladTableValuePromise = await page.waitForSelector(`text="${subscribeTelemValue}"`); page
const ladTableValue = await ladTableValuePromise.textContent(); }) => {
// Create LAD table
expect(ladTableValue).toBe(subscribeTelemValue); await createDomainObjectWithDefaults(page, {
type: 'LAD Table',
name: 'Test LAD Table'
}); });
// Edit LAD table
await page.locator('[title="Edit"]').click();
// Expand the 'My Items' folder in the left tree
await page.locator('.c-tree__item__view-control.c-disclosure-triangle').click();
// Add the Sine Wave Generator to the LAD table and save changes
await page.dragAndDrop('text=Test Sine Wave Generator', '.c-lad-table-wrapper');
await page.locator('button[title="Save"]').click();
await page.locator('text=Save and Finish Editing').click();
// Subscribe to the Sine Wave Generator data
const getTelemValuePromise = await subscribeToTelemetry(page, sineWaveObject.uuid);
// Set an offset of 1 minute and then change the time mode to fixed to set a 1 minute historical window
await setStartOffset(page, { mins: '1' });
await setFixedTimeMode(page);
// On getting data, check if the value found in the LAD table is the most recent value
// from the Sine Wave Generator
const subscribeTelemValue = await getTelemValuePromise;
const ladTableValuePromise = await page.waitForSelector(`text="${subscribeTelemValue}"`);
const ladTableValue = await ladTableValuePromise.textContent();
expect(ladTableValue).toBe(subscribeTelemValue);
});
}); });
/** /**
@ -216,18 +228,20 @@ test.describe('Testing LAD table @unstable', () => {
* @returns {Promise<string>} the formatted sin telemetry value * @returns {Promise<string>} the formatted sin telemetry value
*/ */
async function subscribeToTelemetry(page, objectIdentifier) { async function subscribeToTelemetry(page, objectIdentifier) {
const getTelemValuePromise = new Promise(resolve => page.exposeFunction('getTelemValue', resolve)); const getTelemValuePromise = new Promise((resolve) =>
page.exposeFunction('getTelemValue', resolve)
);
await page.evaluate(async (telemetryIdentifier) => { await page.evaluate(async (telemetryIdentifier) => {
const telemetryObject = await window.openmct.objects.get(telemetryIdentifier); const telemetryObject = await window.openmct.objects.get(telemetryIdentifier);
const metadata = window.openmct.telemetry.getMetadata(telemetryObject); const metadata = window.openmct.telemetry.getMetadata(telemetryObject);
const formats = await window.openmct.telemetry.getFormatMap(metadata); const formats = await window.openmct.telemetry.getFormatMap(metadata);
window.openmct.telemetry.subscribe(telemetryObject, (obj) => { window.openmct.telemetry.subscribe(telemetryObject, (obj) => {
const sinVal = obj.sin; const sinVal = obj.sin;
const formattedSinVal = formats.sin.format(sinVal); const formattedSinVal = formats.sin.format(sinVal);
window.getTelemValue(formattedSinVal); window.getTelemValue(formattedSinVal);
}); });
}, objectIdentifier); }, objectIdentifier);
return getTelemValuePromise; return getTelemValuePromise;
} }

View File

@ -32,417 +32,454 @@ const path = require('path');
const NOTEBOOK_NAME = 'Notebook'; const NOTEBOOK_NAME = 'Notebook';
test.describe('Notebook CRUD Operations', () => { test.describe('Notebook CRUD Operations', () => {
test.fixme('Can create a Notebook Object', async ({ page }) => { test.fixme('Can create a Notebook Object', async ({ page }) => {
//Create domain object //Create domain object
//Newly created notebook should have one Section and one page, 'Unnamed Section'/'Unnamed Page' //Newly created notebook should have one Section and one page, 'Unnamed Section'/'Unnamed Page'
}); });
test.fixme('Can update a Notebook Object', async ({ page }) => {}); test.fixme('Can update a Notebook Object', async ({ page }) => {});
test.fixme('Can view a perviously created Notebook Object', async ({ page }) => {}); test.fixme('Can view a perviously created Notebook Object', async ({ page }) => {});
test.fixme('Can Delete a Notebook Object', async ({ page }) => { test.fixme('Can Delete a Notebook Object', async ({ page }) => {
// Other than non-persistible objects // Other than non-persistible objects
}); });
}); });
test.describe('Default Notebook', () => { test.describe('Default Notebook', () => {
// General Default Notebook statements // General Default Notebook statements
// ## Useful commands: // ## Useful commands:
// 1. - To check default notebook: // 1. - To check default notebook:
// `JSON.parse(localStorage.getItem('notebook-storage'));` // `JSON.parse(localStorage.getItem('notebook-storage'));`
// 1. - Clear default notebook: // 1. - Clear default notebook:
// `localStorage.setItem('notebook-storage', null);` // `localStorage.setItem('notebook-storage', null);`
test.fixme('A newly created Notebook is automatically set as the default notebook if no other notebooks exist', async ({ page }) => { test.fixme(
//Create new notebook 'A newly created Notebook is automatically set as the default notebook if no other notebooks exist',
//Verify Default Notebook Characteristics async ({ page }) => {
}); //Create new notebook
test.fixme('A newly created Notebook is automatically set as the default notebook if at least one other notebook exists', async ({ page }) => { //Verify Default Notebook Characteristics
//Create new notebook A }
//Create second notebook B );
//Verify Non-Default Notebook A Characteristics test.fixme(
//Verify Default Notebook B Characteristics 'A newly created Notebook is automatically set as the default notebook if at least one other notebook exists',
}); async ({ page }) => {
test.fixme('If a default notebook is deleted, the second most recent notebook becomes the default', async ({ page }) => { //Create new notebook A
//Create new notebook A //Create second notebook B
//Create second notebook B //Verify Non-Default Notebook A Characteristics
//Delete Notebook B //Verify Default Notebook B Characteristics
//Verify Default Notebook A Characteristics }
}); );
test.fixme(
'If a default notebook is deleted, the second most recent notebook becomes the default',
async ({ page }) => {
//Create new notebook A
//Create second notebook B
//Delete Notebook B
//Verify Default Notebook A Characteristics
}
);
}); });
test.describe('Notebook section tests', () => { test.describe('Notebook section tests', () => {
//The following test cases are associated with Notebook Sections //The following test cases are associated with Notebook Sections
test.beforeEach(async ({ page }) => { test.beforeEach(async ({ page }) => {
//Navigate to baseURL //Navigate to baseURL
await page.goto('./', { waitUntil: 'domcontentloaded' }); await page.goto('./', { waitUntil: 'domcontentloaded' });
// Create Notebook // Create Notebook
await createDomainObjectWithDefaults(page, { await createDomainObjectWithDefaults(page, {
type: NOTEBOOK_NAME type: NOTEBOOK_NAME
});
}); });
test('Default and new sections are automatically named Unnamed Section with Unnamed Page', async ({ page }) => { });
const notebookSectionNames = page.locator('.c-notebook__sections .c-list__item__name'); test('Default and new sections are automatically named Unnamed Section with Unnamed Page', async ({
const notebookPageNames = page.locator('.c-notebook__pages .c-list__item__name'); page
await expect(notebookSectionNames).toBeHidden(); }) => {
await expect(notebookPageNames).toBeHidden(); const notebookSectionNames = page.locator('.c-notebook__sections .c-list__item__name');
// Expand sidebar const notebookPageNames = page.locator('.c-notebook__pages .c-list__item__name');
await page.locator('.c-notebook__toggle-nav-button').click(); await expect(notebookSectionNames).toBeHidden();
// Check that the default section and page are created and the name matches the defaults await expect(notebookPageNames).toBeHidden();
const defaultSectionName = await notebookSectionNames.innerText(); // Expand sidebar
await expect(notebookSectionNames).toBeVisible(); await page.locator('.c-notebook__toggle-nav-button').click();
expect(defaultSectionName).toBe('Unnamed Section'); // Check that the default section and page are created and the name matches the defaults
const defaultPageName = await notebookPageNames.innerText(); const defaultSectionName = await notebookSectionNames.innerText();
await expect(notebookPageNames).toBeVisible(); await expect(notebookSectionNames).toBeVisible();
expect(defaultPageName).toBe('Unnamed Page'); expect(defaultSectionName).toBe('Unnamed Section');
const defaultPageName = await notebookPageNames.innerText();
await expect(notebookPageNames).toBeVisible();
expect(defaultPageName).toBe('Unnamed Page');
// Add a section // Add a section
await page.locator('.js-sidebar-sections .c-icon-button.icon-plus').click(); await page.locator('.js-sidebar-sections .c-icon-button.icon-plus').click();
// Check that new section and page within the new section match the defaults // Check that new section and page within the new section match the defaults
const newSectionName = await notebookSectionNames.nth(1).innerText(); const newSectionName = await notebookSectionNames.nth(1).innerText();
await expect(notebookSectionNames.nth(1)).toBeVisible(); await expect(notebookSectionNames.nth(1)).toBeVisible();
expect(newSectionName).toBe('Unnamed Section'); expect(newSectionName).toBe('Unnamed Section');
const newPageName = await notebookPageNames.innerText(); const newPageName = await notebookPageNames.innerText();
await expect(notebookPageNames).toBeVisible(); await expect(notebookPageNames).toBeVisible();
expect(newPageName).toBe('Unnamed Page'); expect(newPageName).toBe('Unnamed Page');
});
}); test.fixme('Section selection operations and associated behavior', async ({ page }) => {
test.fixme('Section selection operations and associated behavior', async ({ page }) => { //Create new notebook A
//Create new notebook A //Add Sections until 6 total with no default section/page
//Add Sections until 6 total with no default section/page //Select 3rd section
//Select 3rd section //Delete 4th section
//Delete 4th section //3rd section is still selected
//3rd section is still selected //Delete 3rd section
//Delete 3rd section //1st section is selected
//1st section is selected //Set 3rd section as default
//Set 3rd section as default //Delete 2nd section
//Delete 2nd section //3rd section is still default
//3rd section is still default //Delete 3rd section
//Delete 3rd section //1st is selected and there is no default notebook
//1st is selected and there is no default notebook });
}); test.fixme('Section rename operations', async ({ page }) => {
test.fixme('Section rename operations', async ({ page }) => { // Create a new notebook
// Create a new notebook // Add a section
// Add a section // Rename the section but do not confirm
// Rename the section but do not confirm // Keyboard press 'Escape'
// Keyboard press 'Escape' // Verify that the section name reverts to the default name
// Verify that the section name reverts to the default name // Rename the section but do not confirm
// Rename the section but do not confirm // Keyboard press 'Enter'
// Keyboard press 'Enter' // Verify that the section name is updated
// Verify that the section name is updated // Rename the section to "" (empty string)
// Rename the section to "" (empty string) // Keyboard press 'Enter' to confirm
// Keyboard press 'Enter' to confirm // Verify that the section name reverts to the default name
// Verify that the section name reverts to the default name // Rename the section to something long that overflows the text box
// Rename the section to something long that overflows the text box // Verify that the section name is not truncated while input is active
// Verify that the section name is not truncated while input is active // Confirm the section name edit
// Confirm the section name edit // Verify that the section name is truncated now that input is not active
// Verify that the section name is truncated now that input is not active });
});
}); });
test.describe('Notebook page tests', () => { test.describe('Notebook page tests', () => {
//The following test cases are associated with Notebook Pages //The following test cases are associated with Notebook Pages
test.beforeEach(async ({ page }) => { test.beforeEach(async ({ page }) => {
//Navigate to baseURL //Navigate to baseURL
await page.goto('./', { waitUntil: 'domcontentloaded' }); await page.goto('./', { waitUntil: 'domcontentloaded' });
// Create Notebook // Create Notebook
await createDomainObjectWithDefaults(page, { await createDomainObjectWithDefaults(page, {
type: NOTEBOOK_NAME type: NOTEBOOK_NAME
});
}); });
//Test will need to be implemented after a refactor in #5713 });
// eslint-disable-next-line playwright/no-skipped-test //Test will need to be implemented after a refactor in #5713
test.skip('Delete page popup is removed properly on clicking dropdown again', async ({ page }) => { // eslint-disable-next-line playwright/no-skipped-test
test.info().annotations.push({ test.skip('Delete page popup is removed properly on clicking dropdown again', async ({
type: 'issue', page
description: 'https://github.com/nasa/openmct/issues/5713' }) => {
}); test.info().annotations.push({
// Expand sidebar and add a second page type: 'issue',
await page.locator('.c-notebook__toggle-nav-button').click(); description: 'https://github.com/nasa/openmct/issues/5713'
await page.locator('text=Page Add >> button').click(); });
// Expand sidebar and add a second page
await page.locator('.c-notebook__toggle-nav-button').click();
await page.locator('text=Page Add >> button').click();
// Click on the 2nd page dropdown button and expect the Delete Page option to appear // Click on the 2nd page dropdown button and expect the Delete Page option to appear
await page.locator('button[title="Open context menu"]').nth(2).click(); await page.locator('button[title="Open context menu"]').nth(2).click();
await expect(page.locator('text=Delete Page')).toBeEnabled(); await expect(page.locator('text=Delete Page')).toBeEnabled();
// Clicking on the same page a second time causes the same Delete Page option to recreate // Clicking on the same page a second time causes the same Delete Page option to recreate
await page.locator('button[title="Open context menu"]').nth(2).click(); await page.locator('button[title="Open context menu"]').nth(2).click();
await expect(page.locator('text=Delete Page')).toBeEnabled(); await expect(page.locator('text=Delete Page')).toBeEnabled();
// Clicking on the first page causes the first delete button to detach and recreate on the first page // Clicking on the first page causes the first delete button to detach and recreate on the first page
await page.locator('button[title="Open context menu"]').nth(1).click(); await page.locator('button[title="Open context menu"]').nth(1).click();
const numOfDeletePagePopups = await page.locator('li[title="Delete Page"]').count(); const numOfDeletePagePopups = await page.locator('li[title="Delete Page"]').count();
expect(numOfDeletePagePopups).toBe(1); expect(numOfDeletePagePopups).toBe(1);
}); });
test.fixme('Page selection operations and associated behavior', async ({ page }) => { test.fixme('Page selection operations and associated behavior', async ({ page }) => {
//Create new notebook A //Create new notebook A
//Delete existing Page //Delete existing Page
//New 'Unnamed Page' automatically created //New 'Unnamed Page' automatically created
//Create 6 total Pages without a default page //Create 6 total Pages without a default page
//Select 3rd //Select 3rd
//Delete 3rd //Delete 3rd
//First is now selected //First is now selected
//Set 3rd as default //Set 3rd as default
//Select 2nd page //Select 2nd page
//Delete 2nd page //Delete 2nd page
//3rd (default) is now selected //3rd (default) is now selected
//Set 3rd as default page //Set 3rd as default page
//Select 3rd (default) page //Select 3rd (default) page
//Delete 3rd page //Delete 3rd page
//First is now selected and there is no default notebook //First is now selected and there is no default notebook
}); });
test.fixme('Page rename operations', async ({ page }) => { test.fixme('Page rename operations', async ({ page }) => {
// Create a new notebook // Create a new notebook
// Add a page // Add a page
// Rename the page but do not confirm // Rename the page but do not confirm
// Keyboard press 'Escape' // Keyboard press 'Escape'
// Verify that the page name reverts to the default name // Verify that the page name reverts to the default name
// Rename the page but do not confirm // Rename the page but do not confirm
// Keyboard press 'Enter' // Keyboard press 'Enter'
// Verify that the page name is updated // Verify that the page name is updated
// Rename the page to "" (empty string) // Rename the page to "" (empty string)
// Keyboard press 'Enter' to confirm // Keyboard press 'Enter' to confirm
// Verify that the page name reverts to the default name // Verify that the page name reverts to the default name
// Rename the page to something long that overflows the text box // Rename the page to something long that overflows the text box
// Verify that the page name is not truncated while input is active // Verify that the page name is not truncated while input is active
// Confirm the page name edit // Confirm the page name edit
// Verify that the page name is truncated now that input is not active // Verify that the page name is truncated now that input is not active
}); });
}); });
test.describe('Notebook export tests', () => { test.describe('Notebook export tests', () => {
test.beforeEach(async ({ page }) => { test.beforeEach(async ({ page }) => {
//Navigate to baseURL //Navigate to baseURL
await page.goto('./', { waitUntil: 'domcontentloaded' }); await page.goto('./', { waitUntil: 'domcontentloaded' });
// Create Notebook // Create Notebook
await createDomainObjectWithDefaults(page, { await createDomainObjectWithDefaults(page, {
type: NOTEBOOK_NAME type: NOTEBOOK_NAME
});
}); });
test('can export notebook as text', async ({ page }) => { });
await nbUtils.enterTextEntry(page, `Foo bar entry`); test('can export notebook as text', async ({ page }) => {
// Click on 3 Dot Menu await nbUtils.enterTextEntry(page, `Foo bar entry`);
await page.locator('button[title="More options"]').click(); // Click on 3 Dot Menu
const downloadPromise = page.waitForEvent('download'); await page.locator('button[title="More options"]').click();
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();
await page.getByRole('button', { name: 'Save' }).click(); await page.getByRole('button', { name: 'Save' }).click();
const download = await downloadPromise; const download = await downloadPromise;
const readStream = await download.createReadStream(); const readStream = await download.createReadStream();
const exportedText = await streamToString(readStream); const exportedText = await streamToString(readStream);
expect(exportedText).toContain('Foo bar entry'); expect(exportedText).toContain('Foo bar entry');
}); });
test.fixme('can export multiple notebook entries as text ', async ({ page }) => {}); test.fixme('can export multiple notebook entries as text ', async ({ page }) => {});
test.fixme('can export all notebook entry metdata', async ({ page }) => {}); test.fixme('can export all notebook entry metdata', async ({ page }) => {});
test.fixme('can export all notebook tags', async ({ page }) => {}); test.fixme('can export all notebook tags', async ({ page }) => {});
test.fixme('can export all notebook snapshots', async ({ page }) => {}); test.fixme('can export all notebook snapshots', async ({ page }) => {});
}); });
test.describe('Notebook search tests', () => { test.describe('Notebook search tests', () => {
test.fixme('Can search for a single result', async ({ page }) => {}); test.fixme('Can search for a single result', async ({ page }) => {});
test.fixme('Can search for many results', async ({ page }) => {}); test.fixme('Can search for many results', async ({ page }) => {});
test.fixme('Can search for new and recently modified entries', async ({ page }) => {}); test.fixme('Can search for new and recently modified entries', async ({ page }) => {});
test.fixme('Can search for section text', async ({ page }) => {}); test.fixme('Can search for section text', async ({ page }) => {});
test.fixme('Can search for page text', async ({ page }) => {}); test.fixme('Can search for page text', async ({ page }) => {});
test.fixme('Can search for entry text', async ({ page }) => {}); test.fixme('Can search for entry text', async ({ page }) => {});
}); });
test.describe('Notebook entry tests', () => { test.describe('Notebook entry tests', () => {
// Create Notebook with URL Whitelist // Create Notebook with URL Whitelist
let notebookObject; let notebookObject;
test.beforeEach(async ({ page }) => { test.beforeEach(async ({ page }) => {
// eslint-disable-next-line no-undef // eslint-disable-next-line no-undef
await page.addInitScript({ path: path.join(__dirname, '../../../../helper/', 'addInitNotebookWithUrls.js') }); await page.addInitScript({
await page.goto('./', { waitUntil: 'domcontentloaded' }); path: path.join(__dirname, '../../../../helper/', 'addInitNotebookWithUrls.js')
notebookObject = await createDomainObjectWithDefaults(page, {
type: NOTEBOOK_NAME
});
}); });
test('When a new entry is created, it should be focused and selected', async ({ page }) => { await page.goto('./', { waitUntil: 'domcontentloaded' });
// Navigate to the notebook object
await page.goto(notebookObject.url);
// Click .c-notebook__drag-area notebookObject = await createDomainObjectWithDefaults(page, {
await page.locator('.c-notebook__drag-area').click(); type: NOTEBOOK_NAME
await expect(page.locator('[aria-label="Notebook Entry Input"]')).toBeVisible();
await expect(page.locator('[aria-label="Notebook Entry"]')).toHaveClass(/is-selected/);
}); });
test('When an object is dropped into a notebook, a new entry is created and it should be focused @unstable', async ({ page }) => { });
// Create Overlay Plot test('When a new entry is created, it should be focused and selected', async ({ page }) => {
const overlayPlot = await createDomainObjectWithDefaults(page, { // Navigate to the notebook object
type: 'Overlay Plot' await page.goto(notebookObject.url);
});
// Navigate to the notebook object // Click .c-notebook__drag-area
await page.goto(notebookObject.url); await page.locator('.c-notebook__drag-area').click();
await expect(page.locator('[aria-label="Notebook Entry Input"]')).toBeVisible();
// Reveal the notebook in the tree await expect(page.locator('[aria-label="Notebook Entry"]')).toHaveClass(/is-selected/);
await page.getByTitle('Show selected item in tree').click(); });
test('When an object is dropped into a notebook, a new entry is created and it should be focused @unstable', async ({
await page.getByRole('treeitem', { name: overlayPlot.name }).dragTo(page.locator('.c-notebook__drag-area')); page
}) => {
const embed = page.locator('.c-ne__embed__link'); // Create Overlay Plot
const embedName = await embed.innerText(); const overlayPlot = await createDomainObjectWithDefaults(page, {
type: 'Overlay Plot'
await expect(embed).toHaveClass(/icon-plot-overlay/);
expect(embedName).toBe(overlayPlot.name);
}); });
test('When an object is dropped into a notebooks existing entry, it should be focused @unstable', async ({ page }) => {
// Create Overlay Plot
const overlayPlot = await createDomainObjectWithDefaults(page, {
type: 'Overlay Plot'
});
// Navigate to the notebook object // Navigate to the notebook object
await page.goto(notebookObject.url); await page.goto(notebookObject.url);
// Reveal the notebook in the tree // Reveal the notebook in the tree
await page.getByTitle('Show selected item in tree').click(); await page.getByTitle('Show selected item in tree').click();
await nbUtils.enterTextEntry(page, 'Entry to drop into'); await page
await page.getByRole('treeitem', { name: overlayPlot.name }).dragTo(page.locator('text=Entry to drop into')); .getByRole('treeitem', { name: overlayPlot.name })
.dragTo(page.locator('.c-notebook__drag-area'));
const existingEntry = page.locator('.c-ne__content', { const embed = page.locator('.c-ne__embed__link');
has: page.locator('text="Entry to drop into"') const embedName = await embed.innerText();
});
const embed = existingEntry.locator('.c-ne__embed__link');
const embedName = await embed.innerText();
await expect(embed).toHaveClass(/icon-plot-overlay/); await expect(embed).toHaveClass(/icon-plot-overlay/);
expect(embedName).toBe(overlayPlot.name); expect(embedName).toBe(overlayPlot.name);
});
test('When an object is dropped into a notebooks existing entry, it should be focused @unstable', async ({
page
}) => {
// Create Overlay Plot
const overlayPlot = await createDomainObjectWithDefaults(page, {
type: 'Overlay Plot'
}); });
test.fixme('new entries persist through navigation events without save', async ({ page }) => {});
test('previous and new entries can be deleted', async ({ page }) => {
// Navigate to the notebook object
await page.goto(notebookObject.url);
await nbUtils.enterTextEntry(page, 'First Entry'); // Navigate to the notebook object
await page.hover('text="First Entry"'); await page.goto(notebookObject.url);
await page.click('button[title="Delete this entry"]');
await page.getByRole('button', { name: 'Ok' }).filter({ hasText: 'Ok' }).click(); // Reveal the notebook in the tree
await expect(page.locator('text="First Entry"')).toBeHidden(); await page.getByTitle('Show selected item in tree').click();
await nbUtils.enterTextEntry(page, 'Another First Entry');
await nbUtils.enterTextEntry(page, 'Second Entry'); await nbUtils.enterTextEntry(page, 'Entry to drop into');
await nbUtils.enterTextEntry(page, 'Third Entry'); await page
await page.hover('[aria-label="Notebook Entry"] >> nth=2'); .getByRole('treeitem', { name: overlayPlot.name })
await page.click('button[title="Delete this entry"] >> nth=2'); .dragTo(page.locator('text=Entry to drop into'));
await page.getByRole('button', { name: 'Ok' }).filter({ hasText: 'Ok' }).click();
await expect(page.locator('text="Third Entry"')).toBeHidden(); const existingEntry = page.locator('.c-ne__content', {
await expect(page.locator('text="Another First Entry"')).toBeVisible(); has: page.locator('text="Entry to drop into"')
await expect(page.locator('text="Second Entry"')).toBeVisible();
}); });
test('when a valid link is entered into a notebook entry, it becomes clickable when viewing', async ({ page }) => { const embed = existingEntry.locator('.c-ne__embed__link');
const TEST_LINK = 'http://www.google.com'; const embedName = await embed.innerText();
// Navigate to the notebook object await expect(embed).toHaveClass(/icon-plot-overlay/);
await page.goto(notebookObject.url); expect(embedName).toBe(overlayPlot.name);
});
test.fixme('new entries persist through navigation events without save', async ({ page }) => {});
test('previous and new entries can be deleted', async ({ page }) => {
// Navigate to the notebook object
await page.goto(notebookObject.url);
// Reveal the notebook in the tree await nbUtils.enterTextEntry(page, 'First Entry');
await page.getByTitle('Show selected item in tree').click(); await page.hover('text="First Entry"');
await page.click('button[title="Delete this entry"]');
await page.getByRole('button', { name: 'Ok' }).filter({ hasText: 'Ok' }).click();
await expect(page.locator('text="First Entry"')).toBeHidden();
await nbUtils.enterTextEntry(page, 'Another First Entry');
await nbUtils.enterTextEntry(page, 'Second Entry');
await nbUtils.enterTextEntry(page, 'Third Entry');
await page.hover('[aria-label="Notebook Entry"] >> nth=2');
await page.click('button[title="Delete this entry"] >> nth=2');
await page.getByRole('button', { name: 'Ok' }).filter({ hasText: 'Ok' }).click();
await expect(page.locator('text="Third Entry"')).toBeHidden();
await expect(page.locator('text="Another First Entry"')).toBeVisible();
await expect(page.locator('text="Second Entry"')).toBeVisible();
});
test('when a valid link is entered into a notebook entry, it becomes clickable when viewing', async ({
page
}) => {
const TEST_LINK = 'http://www.google.com';
await nbUtils.enterTextEntry(page, `This should be a link: ${TEST_LINK} is it?`); // Navigate to the notebook object
await page.goto(notebookObject.url);
const validLink = page.locator(`a[href="${TEST_LINK}"]`); // Reveal the notebook in the tree
await page.getByTitle('Show selected item in tree').click();
// Start waiting for popup before clicking. Note no await. await nbUtils.enterTextEntry(page, `This should be a link: ${TEST_LINK} is it?`);
const popupPromise = page.waitForEvent('popup');
await validLink.click(); const validLink = page.locator(`a[href="${TEST_LINK}"]`);
const popup = await popupPromise;
// Wait for the popup to load. // Start waiting for popup before clicking. Note no await.
await popup.waitForLoadState(); const popupPromise = page.waitForEvent('popup');
expect.soft(popup.url()).toContain('www.google.com');
expect(await validLink.count()).toBe(1); await validLink.click();
}); const popup = await popupPromise;
test('when an invalid link is entered into a notebook entry, it does not become clickable when viewing', async ({ page }) => {
const TEST_LINK = 'www.google.com';
// Navigate to the notebook object // Wait for the popup to load.
await page.goto(notebookObject.url); await popup.waitForLoadState();
expect.soft(popup.url()).toContain('www.google.com');
// Reveal the notebook in the tree expect(await validLink.count()).toBe(1);
await page.getByTitle('Show selected item in tree').click(); });
test('when an invalid link is entered into a notebook entry, it does not become clickable when viewing', async ({
page
}) => {
const TEST_LINK = 'www.google.com';
await nbUtils.enterTextEntry(page, `This should NOT be a link: ${TEST_LINK} is it?`); // Navigate to the notebook object
await page.goto(notebookObject.url);
const invalidLink = page.locator(`a[href="${TEST_LINK}"]`); // Reveal the notebook in the tree
await page.getByTitle('Show selected item in tree').click();
expect(await invalidLink.count()).toBe(0); await nbUtils.enterTextEntry(page, `This should NOT be a link: ${TEST_LINK} is it?`);
});
test('when a link is entered, but it is not in the whitelisted urls, it does not become clickable when viewing', async ({ page }) => {
const TEST_LINK = 'http://www.bing.com';
// Navigate to the notebook object const invalidLink = page.locator(`a[href="${TEST_LINK}"]`);
await page.goto(notebookObject.url);
// Reveal the notebook in the tree expect(await invalidLink.count()).toBe(0);
await page.getByTitle('Show selected item in tree').click(); });
test('when a link is entered, but it is not in the whitelisted urls, it does not become clickable when viewing', async ({
page
}) => {
const TEST_LINK = 'http://www.bing.com';
await nbUtils.enterTextEntry(page, `This should NOT be a link: ${TEST_LINK} is it?`); // Navigate to the notebook object
await page.goto(notebookObject.url);
const invalidLink = page.locator(`a[href="${TEST_LINK}"]`); // Reveal the notebook in the tree
await page.getByTitle('Show selected item in tree').click();
expect(await invalidLink.count()).toBe(0); await nbUtils.enterTextEntry(page, `This should NOT be a link: ${TEST_LINK} is it?`);
});
test('when a valid link with a subdomain and a valid domain in the whitelisted urls is entered into a notebook entry, it becomes clickable when viewing', async ({ page }) => {
const INVALID_TEST_LINK = 'http://bing.google.com';
// Navigate to the notebook object const invalidLink = page.locator(`a[href="${TEST_LINK}"]`);
await page.goto(notebookObject.url);
// Reveal the notebook in the tree expect(await invalidLink.count()).toBe(0);
await page.getByTitle('Show selected item in tree').click(); });
test('when a valid link with a subdomain and a valid domain in the whitelisted urls is entered into a notebook entry, it becomes clickable when viewing', async ({
page
}) => {
const INVALID_TEST_LINK = 'http://bing.google.com';
await nbUtils.enterTextEntry(page, `This should be a link: ${INVALID_TEST_LINK} is it?`); // Navigate to the notebook object
await page.goto(notebookObject.url);
const validLink = page.locator(`a[href="${INVALID_TEST_LINK}"]`); // Reveal the notebook in the tree
await page.getByTitle('Show selected item in tree').click();
expect(await validLink.count()).toBe(1); await nbUtils.enterTextEntry(page, `This should be a link: ${INVALID_TEST_LINK} is it?`);
});
test('when a valid secure link is entered into a notebook entry, it becomes clickable when viewing', async ({ page }) => {
const TEST_LINK = 'https://www.google.com';
// Navigate to the notebook object const validLink = page.locator(`a[href="${INVALID_TEST_LINK}"]`);
await page.goto(notebookObject.url);
// Reveal the notebook in the tree expect(await validLink.count()).toBe(1);
await page.getByTitle('Show selected item in tree').click(); });
test('when a valid secure link is entered into a notebook entry, it becomes clickable when viewing', async ({
page
}) => {
const TEST_LINK = 'https://www.google.com';
await nbUtils.enterTextEntry(page, `This should be a link: ${TEST_LINK} is it?`); // Navigate to the notebook object
await page.goto(notebookObject.url);
const validLink = page.locator(`a[href="${TEST_LINK}"]`); // Reveal the notebook in the tree
await page.getByTitle('Show selected item in tree').click();
// Start waiting for popup before clicking. Note no await. await nbUtils.enterTextEntry(page, `This should be a link: ${TEST_LINK} is it?`);
const popupPromise = page.waitForEvent('popup');
await validLink.click(); const validLink = page.locator(`a[href="${TEST_LINK}"]`);
const popup = await popupPromise;
// Wait for the popup to load. // Start waiting for popup before clicking. Note no await.
await popup.waitForLoadState(); const popupPromise = page.waitForEvent('popup');
expect.soft(popup.url()).toContain('www.google.com');
expect(await validLink.count()).toBe(1); await validLink.click();
}); const popup = await popupPromise;
test('when a nefarious link is entered into a notebook entry, it is sanitized when viewing', async ({ page }) => {
const TEST_LINK = 'http://www.google.com?bad=';
const TEST_LINK_BAD = `http://www.google.com?bad=<script>alert('gimme your cookies')</script>`;
// Navigate to the notebook object // Wait for the popup to load.
await page.goto(notebookObject.url); await popup.waitForLoadState();
expect.soft(popup.url()).toContain('www.google.com');
// Reveal the notebook in the tree expect(await validLink.count()).toBe(1);
await page.getByTitle('Show selected item in tree').click(); });
test('when a nefarious link is entered into a notebook entry, it is sanitized when viewing', async ({
page
}) => {
const TEST_LINK = 'http://www.google.com?bad=';
const TEST_LINK_BAD = `http://www.google.com?bad=<script>alert('gimme your cookies')</script>`;
await nbUtils.enterTextEntry(page, `This should be a link, BUT not a bad link: ${TEST_LINK_BAD} is it?`); // Navigate to the notebook object
await page.goto(notebookObject.url);
const sanitizedLink = page.locator(`a[href="${TEST_LINK}"]`); // Reveal the notebook in the tree
const unsanitizedLink = page.locator(`a[href="${TEST_LINK_BAD}"]`); await page.getByTitle('Show selected item in tree').click();
expect.soft(await sanitizedLink.count()).toBe(1); await nbUtils.enterTextEntry(
expect(await unsanitizedLink.count()).toBe(0); page,
}); `This should be a link, BUT not a bad link: ${TEST_LINK_BAD} is it?`
);
const sanitizedLink = page.locator(`a[href="${TEST_LINK}"]`);
const unsanitizedLink = page.locator(`a[href="${TEST_LINK_BAD}"]`);
expect.soft(await sanitizedLink.count()).toBe(1);
expect(await unsanitizedLink.count()).toBe(0);
});
}); });

View File

@ -29,106 +29,135 @@ const { test, expect } = require('../../../../pluginFixtures');
// const nbUtils = require('../../../../helper/notebookUtils'); // const nbUtils = require('../../../../helper/notebookUtils');
test.describe('Snapshot Menu tests', () => { test.describe('Snapshot Menu tests', () => {
test.fixme('When no default notebook is selected, Snapshot Menu dropdown should only have a single option', async ({ page }) => { test.fixme(
// There should be no default notebook 'When no default notebook is selected, Snapshot Menu dropdown should only have a single option',
// Clear default notebook if exists using `localStorage.setItem('notebook-storage', null);` async ({ page }) => {
// refresh page // There should be no default notebook
// Click on 'Notebook Snaphot Menu' // Clear default notebook if exists using `localStorage.setItem('notebook-storage', null);`
// 'save to Notebook Snapshots' should be only option there // refresh page
}); // Click on 'Notebook Snaphot Menu'
test.fixme('When default notebook is updated selected, Snapshot Menu dropdown should list it as the newest option', async ({ page }) => { // 'save to Notebook Snapshots' should be only option there
// Create 2a notebooks }
// Set Notebook A as Default );
// Open Snapshot Menu and note that Notebook A is listed test.fixme(
// Close Snapshot Menu 'When default notebook is updated selected, Snapshot Menu dropdown should list it as the newest option',
// Set Default Notebook to Notebook B async ({ page }) => {
// Open Snapshot Notebook and note that Notebook B is listed // Create 2a notebooks
// Select Default Notebook Option and verify that Snapshot is added to Notebook B // Set Notebook A as Default
}); // Open Snapshot Menu and note that Notebook A is listed
test.fixme('Can add Snapshots via Snapshot Menu and details are correct', async ({ page }) => { // Close Snapshot Menu
//Note this should be a visual test, too // Set Default Notebook to Notebook B
// Create Telemetry object // Open Snapshot Notebook and note that Notebook B is listed
// Create A notebook with many pages and sections. // Select Default Notebook Option and verify that Snapshot is added to Notebook B
// Set page and section defaults to be between first and last of many. i.e. 3 of 5 }
// Navigate to Telemetry object );
// Select Default Notebook Option and verify that Snapshot is added to Notebook A test.fixme('Can add Snapshots via Snapshot Menu and details are correct', async ({ page }) => {
// Verify Snapshot Details appear correctly //Note this should be a visual test, too
}); // Create Telemetry object
test.fixme('Snapshots adjust time conductor', async ({ page }) => { // Create A notebook with many pages and sections.
// Create Telemetry object // Set page and section defaults to be between first and last of many. i.e. 3 of 5
// Set Telemetry object's timeconductor to Fixed time with Start and Endtimes are recorded // Navigate to Telemetry object
// Embed Telemetry object into notebook // Select Default Notebook Option and verify that Snapshot is added to Notebook A
// Set Time Conductor to Local clock // Verify Snapshot Details appear correctly
// Click into embedded telemetry object and verify object appears with same fixed time from record });
}); test.fixme('Snapshots adjust time conductor', async ({ page }) => {
// Create Telemetry object
// Set Telemetry object's timeconductor to Fixed time with Start and Endtimes are recorded
// Embed Telemetry object into notebook
// Set Time Conductor to Local clock
// Click into embedded telemetry object and verify object appears with same fixed time from record
});
}); });
test.describe('Snapshot Container tests', () => { test.describe('Snapshot Container tests', () => {
test.beforeEach(async ({ page }) => { test.beforeEach(async ({ page }) => {
//Navigate to baseURL //Navigate to baseURL
await page.goto('./', { waitUntil: 'domcontentloaded' }); await page.goto('./', { waitUntil: 'domcontentloaded' });
// Create Notebook // Create Notebook
// const notebook = await createDomainObjectWithDefaults(page, { // const notebook = await createDomainObjectWithDefaults(page, {
// type: 'Notebook', // type: 'Notebook',
// name: "Test Notebook" // name: "Test Notebook"
// }); // });
// // Create Overlay Plot // // Create Overlay Plot
// const snapShotObject = await createDomainObjectWithDefaults(page, { // const snapShotObject = await createDomainObjectWithDefaults(page, {
// type: 'Overlay Plot', // type: 'Overlay Plot',
// name: "Dropped Overlay Plot" // name: "Dropped Overlay Plot"
// }); // });
await page.getByRole('button', { name: ' Snapshot ' }).click(); await page.getByRole('button', { name: ' 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.getByRole('button', { name: 'Show' }).click();
});
}); test.fixme('5 Snapshots can be added to a container', async ({ page }) => {});
test.fixme('5 Snapshots can be added to a container', async ({ page }) => {}); test.fixme(
test.fixme('5 Snapshots can be added to a container and Deleted with Delete All action', async ({ page }) => {}); '5 Snapshots can be added to a container and Deleted with Delete All action',
test.fixme('A snapshot can be Deleted from Container with 3 dot action menu', async ({ page }) => {}); async ({ page }) => {}
test.fixme('A snapshot can be Viewed, Annotated, display deleted, and saved from Container with 3 dot action menu', async ({ page }) => { );
await page.locator('.c-snapshot.c-ne__embed').first().getByTitle('More options').click(); test.fixme(
await page.getByRole('menuitem', { name: ' View Snapshot' }).click(); 'A snapshot can be Deleted from Container with 3 dot action menu',
await expect(page.locator('.c-overlay__outer')).toBeVisible(); async ({ page }) => {}
await page.getByTitle('Annotate').click(); );
await expect(page.locator('#snap-annotation-canvas')).toBeVisible(); test.fixme(
await page.getByRole('button', { name: '' }).click(); 'A snapshot can be Viewed, Annotated, display deleted, and saved from Container with 3 dot action menu',
// await expect(page.locator('#snap-annotation-canvas')).not.toBeVisible(); async ({ page }) => {
await page.getByRole('button', { name: 'Save' }).click(); await page.locator('.c-snapshot.c-ne__embed').first().getByTitle('More options').click();
await page.getByRole('button', { name: 'Done' }).click(); await page.getByRole('menuitem', { name: ' View Snapshot' }).click();
//await expect(await page.locator) await expect(page.locator('.c-overlay__outer')).toBeVisible();
}); await page.getByTitle('Annotate').click();
test('A snapshot can be Quick Viewed from Container with 3 dot action menu', async ({ page }) => { await expect(page.locator('#snap-annotation-canvas')).toBeVisible();
await page.locator('.c-snapshot.c-ne__embed').first().getByTitle('More options').click(); await page.getByRole('button', { name: '' }).click();
await page.getByRole('menuitem', { name: 'Quick View' }).click(); // await expect(page.locator('#snap-annotation-canvas')).not.toBeVisible();
await expect(page.locator('.c-overlay__outer')).toBeVisible(); await page.getByRole('button', { name: 'Save' }).click();
}); await page.getByRole('button', { name: 'Done' }).click();
test.fixme('A snapshot can be Navigated To from Container with 3 dot action menu', async ({ page }) => {}); //await expect(await page.locator)
test.fixme('A snapshot can be Navigated To Item in Time from Container with 3 dot action menu', async ({ page }) => {}); }
test.fixme('A snapshot Container can be open and closed', async ({ page }) => {}); );
test.fixme('Can add object to Snapshot container and pull into notebook and create a new entry', async ({ page }) => { test('A snapshot can be Quick Viewed from Container with 3 dot action menu', async ({ page }) => {
//Create Notebook await page.locator('.c-snapshot.c-ne__embed').first().getByTitle('More options').click();
//Create Telemetry Object await page.getByRole('menuitem', { name: 'Quick View' }).click();
//From Telemetry Object, use 'save to Notebook Snapshots' await expect(page.locator('.c-overlay__outer')).toBeVisible();
//Snapshots indicator should blink, click on it to view snapshots });
//Navigate to Notebook test.fixme(
//Drag and Drop onto droppable area for new entry 'A snapshot can be Navigated To from Container with 3 dot action menu',
//New Entry created with given snapshot added async ({ page }) => {}
//Snapshot removed from container? );
}); test.fixme(
test.fixme('Can add object to Snapshot container and pull into notebook and existing entry', async ({ page }) => { 'A snapshot can be Navigated To Item in Time from Container with 3 dot action menu',
//Create Notebook async ({ page }) => {}
//Create Telemetry Object );
//From Telemetry Object, use 'save to Notebook Snapshots' test.fixme('A snapshot Container can be open and closed', async ({ page }) => {});
//Snapshots indicator should blink, click on it to view snapshots test.fixme(
//Navigate to Notebook 'Can add object to Snapshot container and pull into notebook and create a new entry',
//Drag and Drop into exiting entry async ({ page }) => {
//Existing Entry updated with given snapshot //Create Notebook
//Snapshot removed from container? //Create Telemetry Object
}); //From Telemetry Object, use 'save to Notebook Snapshots'
test.fixme('Verify Embedded options for PNG, JPG, and Annotate work correctly', async ({ page }) => { //Snapshots indicator should blink, click on it to view snapshots
//Add snapshot to container //Navigate to Notebook
//Verify PNG, JPG, and Annotate buttons work correctly //Drag and Drop onto droppable area for new entry
}); //New Entry created with given snapshot added
//Snapshot removed from container?
}
);
test.fixme(
'Can add object to Snapshot container and pull into notebook and existing entry',
async ({ page }) => {
//Create Notebook
//Create Telemetry Object
//From Telemetry Object, use 'save to Notebook Snapshots'
//Snapshots indicator should blink, click on it to view snapshots
//Navigate to Notebook
//Drag and Drop into exiting entry
//Existing Entry updated with given snapshot
//Snapshot removed from container?
}
);
test.fixme(
'Verify Embedded options for PNG, JPG, and Annotate work correctly',
async ({ page }) => {
//Add snapshot to container
//Verify PNG, JPG, and Annotate buttons work correctly
}
);
}); });

View File

@ -29,180 +29,184 @@ const { createDomainObjectWithDefaults } = require('../../../../appActions');
const nbUtils = require('../../../../helper/notebookUtils'); const nbUtils = require('../../../../helper/notebookUtils');
test.describe('Notebook Tests with CouchDB @couchdb', () => { test.describe('Notebook Tests with CouchDB @couchdb', () => {
let testNotebook; let testNotebook;
test.beforeEach(async ({ page }) => { test.beforeEach(async ({ page }) => {
//Navigate to baseURL //Navigate to baseURL
await page.goto('./', { waitUntil: 'domcontentloaded' }); await page.goto('./', { waitUntil: 'domcontentloaded' });
// Create Notebook // Create Notebook
testNotebook = await createDomainObjectWithDefaults(page, {type: 'Notebook' }); testNotebook = await createDomainObjectWithDefaults(page, { type: 'Notebook' });
await page.goto(testNotebook.url, { waitUntil: 'networkidle'}); await page.goto(testNotebook.url, { waitUntil: 'networkidle' });
});
test('Inspect Notebook Entry Network Requests', async ({ page }) => {
//Ensure we're on the annotations Tab in the inspector
await page.getByText('Annotations').click();
// Expand sidebar
await page.locator('.c-notebook__toggle-nav-button').click();
// Collect all request events to count and assert after notebook action
let notebookElementsRequests = [];
page.on('request', (request) => notebookElementsRequests.push(request));
//Clicking Add Page generates
let [notebookUrlRequest, allDocsRequest] = await Promise.all([
// Waits for the next request with the specified url
page.waitForRequest(`**/openmct/${testNotebook.uuid}`),
page.waitForRequest('**/openmct/_all_docs?include_docs=true'),
// Triggers the request
page.click('[aria-label="Add Page"]')
]);
// Ensures that there are no other network requests
await page.waitForLoadState('networkidle');
// Assert that only two requests are made
// Network Requests are:
// 1) The actual POST to create the page
// 2) The shared worker event from 👆 request
expect(notebookElementsRequests.length).toBe(2);
// Assert on request object
expect(notebookUrlRequest.postDataJSON().metadata.name).toBe(testNotebook.name);
expect(notebookUrlRequest.postDataJSON().model.persisted).toBeGreaterThanOrEqual(
notebookUrlRequest.postDataJSON().model.modified
);
expect(allDocsRequest.postDataJSON().keys).toContain(testNotebook.uuid);
// Add an entry
// Network Requests are:
// 1) The actual POST to create the entry
// 2) The shared worker event from 👆 POST request
notebookElementsRequests = [];
await nbUtils.enterTextEntry(page, 'First Entry');
await page.waitForLoadState('networkidle');
expect(notebookElementsRequests.length).toBeLessThanOrEqual(2);
// Add some tags
// Network Requests are for each tag creation are:
// 1) Getting the original path of the parent object
// 2) Getting the original path of the grandparent object (recursive call)
// 3) Creating the annotation/tag object
// 4) The shared worker event from 👆 POST request
// 5) Mutate notebook domain object's annotationModified property
// 6) The shared worker event from 👆 POST request
// 7) Notebooks fetching new annotations due to annotationModified changed
// 8) The update of the notebook domain's object's modified property
// 9) The shared worker event from 👆 POST request
// 10) Entry is timestamped
// 11) The shared worker event from 👆 POST request
notebookElementsRequests = [];
await addTagAndAwaitNetwork(page, 'Driving');
expect(filterNonFetchRequests(notebookElementsRequests).length).toBeLessThanOrEqual(11);
notebookElementsRequests = [];
await addTagAndAwaitNetwork(page, 'Drilling');
expect(filterNonFetchRequests(notebookElementsRequests).length).toBeLessThanOrEqual(11);
notebookElementsRequests = [];
await addTagAndAwaitNetwork(page, 'Science');
expect(filterNonFetchRequests(notebookElementsRequests).length).toBeLessThanOrEqual(11);
// Delete all the tags
// Network requests are:
// 1) Send POST to mutate _delete property to true on annotation with tag
// 2) The shared worker event from 👆 POST request
// 3) Timestamp update on entry
// 4) The shared worker event from 👆 POST request
// This happens for 3 tags so 12 requests
notebookElementsRequests = [];
await removeTagAndAwaitNetwork(page, 'Driving');
await removeTagAndAwaitNetwork(page, 'Drilling');
await removeTagAndAwaitNetwork(page, 'Science');
expect(filterNonFetchRequests(notebookElementsRequests).length).toBeLessThanOrEqual(12);
// Add two more pages
await page.click('[aria-label="Add Page"]');
await page.click('[aria-label="Add Page"]');
// Add three entries
await nbUtils.enterTextEntry(page, 'First Entry');
await nbUtils.enterTextEntry(page, 'Second Entry');
await nbUtils.enterTextEntry(page, 'Third Entry');
// Add three tags
await addTagAndAwaitNetwork(page, 'Science');
await addTagAndAwaitNetwork(page, 'Drilling');
await addTagAndAwaitNetwork(page, 'Driving');
// Add a fourth entry
// Network requests are:
// 1) Send POST to add new entry
// 2) The shared worker event from 👆 POST request
// 3) Timestamp update on entry
// 4) The shared worker event from 👆 POST request
notebookElementsRequests = [];
await nbUtils.enterTextEntry(page, 'Fourth Entry');
page.waitForLoadState('networkidle');
expect(filterNonFetchRequests(notebookElementsRequests).length).toBeLessThanOrEqual(4);
// Add a fifth entry
// Network requests are:
// 1) Send POST to add new entry
// 2) The shared worker event from 👆 POST request
// 3) Timestamp update on entry
// 4) The shared worker event from 👆 POST request
notebookElementsRequests = [];
await nbUtils.enterTextEntry(page, 'Fifth Entry');
page.waitForLoadState('networkidle');
expect(filterNonFetchRequests(notebookElementsRequests).length).toBeLessThanOrEqual(4);
// Add a sixth entry
// 1) Send POST to add new entry
// 2) The shared worker event from 👆 POST request
// 3) Timestamp update on entry
// 4) The shared worker event from 👆 POST request
notebookElementsRequests = [];
await nbUtils.enterTextEntry(page, 'Sixth Entry');
page.waitForLoadState('networkidle');
expect(filterNonFetchRequests(notebookElementsRequests).length).toBeLessThanOrEqual(4);
});
test('Search tests', async ({ page }) => {
test.info().annotations.push({
type: 'issue',
description: 'https://github.com/akhenry/openmct-yamcs/issues/69'
}); });
await page.getByText('Annotations').click();
await nbUtils.enterTextEntry(page, 'First Entry');
test('Inspect Notebook Entry Network Requests', async ({ page }) => { // Add three tags
//Ensure we're on the annotations Tab in the inspector await addTagAndAwaitNetwork(page, 'Science');
await page.getByText('Annotations').click(); await addTagAndAwaitNetwork(page, 'Drilling');
// Expand sidebar await addTagAndAwaitNetwork(page, 'Driving');
await page.locator('.c-notebook__toggle-nav-button').click();
// Collect all request events to count and assert after notebook action await page.locator('[aria-label="OpenMCT Search"] input[type="search"]').click();
let notebookElementsRequests = []; //Partial match for "Science" should only return Science
page.on('request', (request) => notebookElementsRequests.push(request)); 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="Search Result"]').first()).not.toContainText('Driving');
await expect(page.locator('[aria-label="Search Result"]').first()).not.toContainText(
'Drilling'
);
//Clicking Add Page generates //Searching for a tag which does not exist should return an empty result
let [notebookUrlRequest, allDocsRequest] = await Promise.all([ await page.locator('[aria-label="OpenMCT Search"] input[type="search"]').click();
// Waits for the next request with the specified url await page.locator('[aria-label="OpenMCT Search"] input[type="search"]').fill('Xq');
page.waitForRequest(`**/openmct/${testNotebook.uuid}`), await expect(page.locator('text=No results found')).toBeVisible();
page.waitForRequest('**/openmct/_all_docs?include_docs=true'), });
// Triggers the request
page.click('[aria-label="Add Page"]')
]);
// Ensures that there are no other network requests
await page.waitForLoadState('networkidle');
// Assert that only two requests are made
// Network Requests are:
// 1) The actual POST to create the page
// 2) The shared worker event from 👆 request
expect(notebookElementsRequests.length).toBe(2);
// Assert on request object
expect(notebookUrlRequest.postDataJSON().metadata.name).toBe(testNotebook.name);
expect(notebookUrlRequest.postDataJSON().model.persisted).toBeGreaterThanOrEqual(notebookUrlRequest.postDataJSON().model.modified);
expect(allDocsRequest.postDataJSON().keys).toContain(testNotebook.uuid);
// Add an entry
// Network Requests are:
// 1) The actual POST to create the entry
// 2) The shared worker event from 👆 POST request
notebookElementsRequests = [];
await nbUtils.enterTextEntry(page, 'First Entry');
await page.waitForLoadState('networkidle');
expect(notebookElementsRequests.length).toBeLessThanOrEqual(2);
// Add some tags
// Network Requests are for each tag creation are:
// 1) Getting the original path of the parent object
// 2) Getting the original path of the grandparent object (recursive call)
// 3) Creating the annotation/tag object
// 4) The shared worker event from 👆 POST request
// 5) Mutate notebook domain object's annotationModified property
// 6) The shared worker event from 👆 POST request
// 7) Notebooks fetching new annotations due to annotationModified changed
// 8) The update of the notebook domain's object's modified property
// 9) The shared worker event from 👆 POST request
// 10) Entry is timestamped
// 11) The shared worker event from 👆 POST request
notebookElementsRequests = [];
await addTagAndAwaitNetwork(page, 'Driving');
expect(filterNonFetchRequests(notebookElementsRequests).length).toBeLessThanOrEqual(11);
notebookElementsRequests = [];
await addTagAndAwaitNetwork(page, 'Drilling');
expect(filterNonFetchRequests(notebookElementsRequests).length).toBeLessThanOrEqual(11);
notebookElementsRequests = [];
await addTagAndAwaitNetwork(page, 'Science');
expect(filterNonFetchRequests(notebookElementsRequests).length).toBeLessThanOrEqual(11);
// Delete all the tags
// Network requests are:
// 1) Send POST to mutate _delete property to true on annotation with tag
// 2) The shared worker event from 👆 POST request
// 3) Timestamp update on entry
// 4) The shared worker event from 👆 POST request
// This happens for 3 tags so 12 requests
notebookElementsRequests = [];
await removeTagAndAwaitNetwork(page, 'Driving');
await removeTagAndAwaitNetwork(page, 'Drilling');
await removeTagAndAwaitNetwork(page, 'Science');
expect(filterNonFetchRequests(notebookElementsRequests).length).toBeLessThanOrEqual(12);
// Add two more pages
await page.click('[aria-label="Add Page"]');
await page.click('[aria-label="Add Page"]');
// Add three entries
await nbUtils.enterTextEntry(page, 'First Entry');
await nbUtils.enterTextEntry(page, 'Second Entry');
await nbUtils.enterTextEntry(page, 'Third Entry');
// Add three tags
await addTagAndAwaitNetwork(page, 'Science');
await addTagAndAwaitNetwork(page, 'Drilling');
await addTagAndAwaitNetwork(page, 'Driving');
// Add a fourth entry
// Network requests are:
// 1) Send POST to add new entry
// 2) The shared worker event from 👆 POST request
// 3) Timestamp update on entry
// 4) The shared worker event from 👆 POST request
notebookElementsRequests = [];
await nbUtils.enterTextEntry(page, 'Fourth Entry');
page.waitForLoadState('networkidle');
expect(filterNonFetchRequests(notebookElementsRequests).length).toBeLessThanOrEqual(4);
// Add a fifth entry
// Network requests are:
// 1) Send POST to add new entry
// 2) The shared worker event from 👆 POST request
// 3) Timestamp update on entry
// 4) The shared worker event from 👆 POST request
notebookElementsRequests = [];
await nbUtils.enterTextEntry(page, 'Fifth Entry');
page.waitForLoadState('networkidle');
expect(filterNonFetchRequests(notebookElementsRequests).length).toBeLessThanOrEqual(4);
// Add a sixth entry
// 1) Send POST to add new entry
// 2) The shared worker event from 👆 POST request
// 3) Timestamp update on entry
// 4) The shared worker event from 👆 POST request
notebookElementsRequests = [];
await nbUtils.enterTextEntry(page, 'Sixth Entry');
page.waitForLoadState('networkidle');
expect(filterNonFetchRequests(notebookElementsRequests).length).toBeLessThanOrEqual(4);
});
test('Search tests', async ({ page }) => {
test.info().annotations.push({
type: 'issue',
description: 'https://github.com/akhenry/openmct-yamcs/issues/69'
});
await page.getByText('Annotations').click();
await nbUtils.enterTextEntry(page, 'First Entry');
// Add three tags
await addTagAndAwaitNetwork(page, 'Science');
await addTagAndAwaitNetwork(page, 'Drilling');
await addTagAndAwaitNetwork(page, 'Driving');
await page.locator('[aria-label="OpenMCT Search"] input[type="search"]').click();
//Partial match for "Science" should only return Science
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="Search Result"]').first()).not.toContainText("Driving");
await expect(page.locator('[aria-label="Search Result"]').first()).not.toContainText("Drilling");
//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"]').fill('Xq');
await expect(page.locator('text=No results found')).toBeVisible();
});
}); });
// Try to reduce indeterminism of browser requests by only returning fetch requests. // Try to reduce indeterminism of browser requests by only returning fetch requests.
// Filter out preflight CORS, fetching stylesheets, page icons, etc. that can occur during tests // Filter out preflight CORS, fetching stylesheets, page icons, etc. that can occur during tests
function filterNonFetchRequests(requests) { function filterNonFetchRequests(requests) {
return requests.filter(request => { return requests.filter((request) => {
return (request.resourceType() === 'fetch'); return request.resourceType() === 'fetch';
}); });
} }
/** /**
@ -212,17 +216,17 @@ function filterNonFetchRequests(requests) {
* @param {string} tagName * @param {string} tagName
*/ */
async function addTagAndAwaitNetwork(page, tagName) { async function addTagAndAwaitNetwork(page, tagName) {
await page.hover(`button:has-text("Add Tag")`); await page.hover(`button:has-text("Add Tag")`);
await page.locator(`button:has-text("Add Tag")`).click(); await page.locator(`button:has-text("Add Tag")`).click();
await page.locator('[placeholder="Type to select tag"]').click(); await page.locator('[placeholder="Type to select tag"]').click();
await Promise.all([ await Promise.all([
// Waits for the next request with the specified url // Waits for the next request with the specified url
page.waitForRequest('**/openmct/_all_docs?include_docs=true'), page.waitForRequest('**/openmct/_all_docs?include_docs=true'),
// Triggers the request // Triggers the request
page.locator(`[aria-label="Autocomplete Options"] >> text=${tagName}`).click(), page.locator(`[aria-label="Autocomplete Options"] >> text=${tagName}`).click(),
expect(page.locator(`[aria-label="Tag"]:has-text("${tagName}")`)).toBeVisible() expect(page.locator(`[aria-label="Tag"]:has-text("${tagName}")`)).toBeVisible()
]); ]);
await page.waitForLoadState('networkidle'); await page.waitForLoadState('networkidle');
} }
/** /**
@ -232,12 +236,14 @@ async function addTagAndAwaitNetwork(page, tagName) {
* @param {string} tagName * @param {string} tagName
*/ */
async function removeTagAndAwaitNetwork(page, tagName) { async function removeTagAndAwaitNetwork(page, tagName) {
await page.hover(`[aria-label="Tag"]:has-text("${tagName}")`); await page.hover(`[aria-label="Tag"]:has-text("${tagName}")`);
await Promise.all([ await Promise.all([
page.locator(`[aria-label="Remove tag ${tagName}"]`).click(), page.locator(`[aria-label="Remove tag ${tagName}"]`).click(),
//With this pattern, we're awaiting the response but asserting on the request payload. //With this pattern, we're awaiting the response but asserting on the request payload.
page.waitForResponse(resp => resp.request().postData().includes(`"_deleted":true`) && resp.status() === 201) page.waitForResponse(
]); (resp) => resp.request().postData().includes(`"_deleted":true`) && resp.status() === 201
await expect(page.locator(`[aria-label="Tag"]:has-text("${tagName}")`)).toBeHidden(); )
await page.waitForLoadState('networkidle'); ]);
await expect(page.locator(`[aria-label="Tag"]:has-text("${tagName}")`)).toBeHidden();
await page.waitForLoadState('networkidle');
} }

View File

@ -21,7 +21,10 @@
*****************************************************************************/ *****************************************************************************/
/* global __dirname */ /* global __dirname */
const { test, expect, streamToString } = require('../../../../pluginFixtures'); const { test, expect, streamToString } = require('../../../../pluginFixtures');
const { openObjectTreeContextMenu, createDomainObjectWithDefaults } = require('../../../../appActions'); const {
openObjectTreeContextMenu,
createDomainObjectWithDefaults
} = require('../../../../appActions');
const path = require('path'); const path = require('path');
const nbUtils = require('../../../../helper/notebookUtils'); const nbUtils = require('../../../../helper/notebookUtils');
@ -30,183 +33,186 @@ const TEST_TEXT_NAME = 'Test Page';
const CUSTOM_NAME = 'CUSTOM_NAME'; const CUSTOM_NAME = 'CUSTOM_NAME';
test.describe('Restricted Notebook', () => { test.describe('Restricted Notebook', () => {
let notebook; let notebook;
test.beforeEach(async ({ page }) => { test.beforeEach(async ({ page }) => {
notebook = await startAndAddRestrictedNotebookObject(page); notebook = await startAndAddRestrictedNotebookObject(page);
}); });
test('Can be renamed @addInit', async ({ page }) => { test('Can be renamed @addInit', async ({ page }) => {
await expect(page.locator('.l-browse-bar__object-name')).toContainText(`${notebook.name}`); await expect(page.locator('.l-browse-bar__object-name')).toContainText(`${notebook.name}`);
}); });
test('Can be deleted if there are no locked pages @addInit', async ({ page }) => { test('Can be deleted if there are no locked pages @addInit', async ({ page }) => {
await openObjectTreeContextMenu(page, notebook.url); await openObjectTreeContextMenu(page, notebook.url);
const menuOptions = page.locator('.c-menu ul'); const menuOptions = page.locator('.c-menu ul');
await expect.soft(menuOptions).toContainText('Remove'); await expect.soft(menuOptions).toContainText('Remove');
const restrictedNotebookTreeObject = page.locator(`a:has-text("${notebook.name}")`); const restrictedNotebookTreeObject = page.locator(`a:has-text("${notebook.name}")`);
// notebook tree object exists // notebook tree object exists
expect.soft(await restrictedNotebookTreeObject.count()).toEqual(1); expect.soft(await restrictedNotebookTreeObject.count()).toEqual(1);
// Click Remove Text // Click Remove Text
await page.locator('li[role="menuitem"]:has-text("Remove")').click(); await page.locator('li[role="menuitem"]:has-text("Remove")').click();
// Click 'OK' on confirmation window and wait for save banner to appear // Click 'OK' on confirmation window and wait for save banner to appear
await Promise.all([ await Promise.all([
page.waitForNavigation(), page.waitForNavigation(),
page.locator('button:has-text("OK")').click(), page.locator('button:has-text("OK")').click(),
page.waitForSelector('.c-message-banner__message') page.waitForSelector('.c-message-banner__message')
]); ]);
// has been deleted // has been deleted
expect(await restrictedNotebookTreeObject.count()).toEqual(0); expect(await restrictedNotebookTreeObject.count()).toEqual(0);
}); });
test('Can be locked if at least one page has one entry @addInit', async ({ page }) => { test('Can be locked if at least one page has one entry @addInit', async ({ page }) => {
await nbUtils.enterTextEntry(page, TEST_TEXT);
await nbUtils.enterTextEntry(page, TEST_TEXT);
const commitButton = page.locator('button:has-text("Commit Entries")');
expect(await commitButton.count()).toEqual(1);
});
const commitButton = page.locator('button:has-text("Commit Entries")');
expect(await commitButton.count()).toEqual(1);
});
}); });
test.describe('Restricted Notebook with at least one entry and with the page locked @addInit', () => { test.describe('Restricted Notebook with at least one entry and with the page locked @addInit', () => {
let notebook; let notebook;
test.beforeEach(async ({ page }) => { test.beforeEach(async ({ page }) => {
notebook = await startAndAddRestrictedNotebookObject(page); notebook = await startAndAddRestrictedNotebookObject(page);
await nbUtils.enterTextEntry(page, TEST_TEXT); await nbUtils.enterTextEntry(page, TEST_TEXT);
await lockPage(page); await lockPage(page);
// open sidebar // open sidebar
await page.locator('button.c-notebook__toggle-nav-button').click(); await page.locator('button.c-notebook__toggle-nav-button').click();
}); });
test('Locked page should now be in a locked state @addInit @unstable', async ({ page }, testInfo) => { test('Locked page should now be in a locked state @addInit @unstable', async ({
// eslint-disable-next-line playwright/no-skipped-test page
test.skip(testInfo.project === 'chrome-beta', "Test is unreliable on chrome-beta"); }, testInfo) => {
// main lock message on page // eslint-disable-next-line playwright/no-skipped-test
const lockMessage = page.locator('text=This page has been committed and cannot be modified or removed'); test.skip(testInfo.project === 'chrome-beta', 'Test is unreliable on chrome-beta');
expect.soft(await lockMessage.count()).toEqual(1); // main lock message on page
const lockMessage = page.locator(
'text=This page has been committed and cannot be modified or removed'
);
expect.soft(await lockMessage.count()).toEqual(1);
// lock icon on page in sidebar // lock icon on page in sidebar
const pageLockIcon = page.locator('ul.c-notebook__pages li div.icon-lock'); const pageLockIcon = page.locator('ul.c-notebook__pages li div.icon-lock');
expect.soft(await pageLockIcon.count()).toEqual(1); expect.soft(await pageLockIcon.count()).toEqual(1);
// no way to remove a restricted notebook with a locked page // no way to remove a restricted notebook with a locked page
await openObjectTreeContextMenu(page, notebook.url); await openObjectTreeContextMenu(page, notebook.url);
const menuOptions = page.locator('.c-menu ul'); const menuOptions = page.locator('.c-menu ul');
await expect(menuOptions).not.toContainText('Remove'); await expect(menuOptions).not.toContainText('Remove');
}); });
test('Can still: add page, rename, add entry, delete unlocked pages @addInit', async ({ page }) => { test('Can still: add page, rename, add entry, delete unlocked pages @addInit', async ({
// Add a new page to the section page
await page.getByRole('button', { name: 'Add Page' }).click(); }) => {
// Focus the new page by clicking it // Add a new page to the section
await page.getByText('Unnamed Page').nth(1).click(); await page.getByRole('button', { name: 'Add Page' }).click();
// Rename the new page // Focus the new page by clicking it
await page.getByText('Unnamed Page').nth(1).fill(TEST_TEXT_NAME); await page.getByText('Unnamed Page').nth(1).click();
// Rename the new page
await page.getByText('Unnamed Page').nth(1).fill(TEST_TEXT_NAME);
// expect to be able to rename unlocked pages // expect to be able to rename unlocked pages
const newPageElement = page.getByText(TEST_TEXT_NAME); const newPageElement = page.getByText(TEST_TEXT_NAME);
const newPageCount = await newPageElement.count(); const newPageCount = await newPageElement.count();
await newPageElement.press('Enter'); // exit contenteditable state await newPageElement.press('Enter'); // exit contenteditable state
expect.soft(newPageCount).toEqual(1); expect.soft(newPageCount).toEqual(1);
// enter test text // enter test text
await nbUtils.enterTextEntry(page, TEST_TEXT); await nbUtils.enterTextEntry(page, TEST_TEXT);
// expect new page to be lockable // expect new page to be lockable
const commitButton = page.getByRole('button', { name: ' Commit Entries' }); const commitButton = page.getByRole('button', { name: ' Commit Entries' });
expect.soft(await commitButton.count()).toEqual(1); expect.soft(await commitButton.count()).toEqual(1);
// Click the context menu button for the new page // Click the context menu button for the new page
await page.getByTitle('Open context menu').click(); await page.getByTitle('Open context menu').click();
// Delete the page // Delete the page
await page.getByRole('listitem', { name: 'Delete Page' }).click(); await page.getByRole('listitem', { name: 'Delete Page' }).click();
// Click OK button // Click OK button
await page.getByRole('button', { name: 'Ok' }).click(); await page.getByRole('button', { name: 'Ok' }).click();
// deleted page, should no longer exist // deleted page, should no longer exist
const deletedPageElement = page.getByText(TEST_TEXT_NAME); const deletedPageElement = page.getByText(TEST_TEXT_NAME);
expect(await deletedPageElement.count()).toEqual(0); expect(await deletedPageElement.count()).toEqual(0);
}); });
}); });
test.describe('Restricted Notebook with a page locked and with an embed @addInit', () => { test.describe('Restricted Notebook with a page locked and with an embed @addInit', () => {
test.beforeEach(async ({ page }) => {
const notebook = await startAndAddRestrictedNotebookObject(page);
await nbUtils.dragAndDropEmbed(page, notebook);
});
test.beforeEach(async ({ page }) => { test('Allows embeds to be deleted if page unlocked @addInit', async ({ page }) => {
const notebook = await startAndAddRestrictedNotebookObject(page); // Click .c-ne__embed__name .c-popup-menu-button
await nbUtils.dragAndDropEmbed(page, notebook); await page.locator('.c-ne__embed__name .c-icon-button').click(); // embed popup menu
});
test('Allows embeds to be deleted if page unlocked @addInit', async ({ page }) => { const embedMenu = page.locator('body >> .c-menu');
// Click .c-ne__embed__name .c-popup-menu-button await expect(embedMenu).toContainText('Remove This Embed');
await page.locator('.c-ne__embed__name .c-icon-button').click(); // embed popup menu });
const embedMenu = page.locator('body >> .c-menu'); test('Disallows embeds to be deleted if page locked @addInit', async ({ page }) => {
await expect(embedMenu).toContainText('Remove This Embed'); await lockPage(page);
}); // Click .c-ne__embed__name .c-popup-menu-button
await page.locator('.c-ne__embed__name .c-icon-button').click(); // embed popup menu
test('Disallows embeds to be deleted if page locked @addInit', async ({ page }) => {
await lockPage(page);
// Click .c-ne__embed__name .c-popup-menu-button
await page.locator('.c-ne__embed__name .c-icon-button').click(); // embed popup menu
const embedMenu = page.locator('body >> .c-menu');
await expect(embedMenu).not.toContainText('Remove This Embed');
});
const embedMenu = page.locator('body >> .c-menu');
await expect(embedMenu).not.toContainText('Remove This Embed');
});
}); });
test.describe('can export restricted notebook as text', () => { test.describe('can export restricted notebook as text', () => {
test.beforeEach(async ({ page }) => { test.beforeEach(async ({ page }) => {
await startAndAddRestrictedNotebookObject(page); await startAndAddRestrictedNotebookObject(page);
}); });
test('basic functionality ', async ({ page }) => { test('basic functionality ', 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 options"]').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();
await page.getByRole('button', { name: 'Save' }).click(); await page.getByRole('button', { name: 'Save' }).click();
const download = await downloadPromise; const download = await downloadPromise;
const readStream = await download.createReadStream(); const readStream = await download.createReadStream();
const exportedText = await streamToString(readStream); const exportedText = await streamToString(readStream);
expect(exportedText).toContain('Foo bar entry'); expect(exportedText).toContain('Foo bar entry');
});
}); test.fixme('can export multiple notebook entries as text ', async ({ page }) => {});
test.fixme('can export all notebook entry metdata', async ({ page }) => {});
test.fixme('can export multiple notebook entries as text ', async ({ page }) => {}); test.fixme('can export all notebook tags', async ({ page }) => {});
test.fixme('can export all notebook entry metdata', async ({ page }) => {}); test.fixme('can export all notebook snapshots', async ({ page }) => {});
test.fixme('can export all notebook tags', async ({ page }) => {});
test.fixme('can export all notebook snapshots', async ({ page }) => {});
}); });
/** /**
* @param {import('@playwright/test').Page} page * @param {import('@playwright/test').Page} page
*/ */
async function startAndAddRestrictedNotebookObject(page) { async function startAndAddRestrictedNotebookObject(page) {
await page.addInitScript({ path: path.join(__dirname, '../../../../helper/', 'addInitRestrictedNotebook.js') }); await page.addInitScript({
await page.goto('./', { waitUntil: 'domcontentloaded' }); path: path.join(__dirname, '../../../../helper/', 'addInitRestrictedNotebook.js')
});
await page.goto('./', { waitUntil: 'domcontentloaded' });
return createDomainObjectWithDefaults(page, { type: CUSTOM_NAME }); return createDomainObjectWithDefaults(page, { type: CUSTOM_NAME });
} }
/** /**
* @param {import('@playwright/test').Page} page * @param {import('@playwright/test').Page} page
*/ */
async function lockPage(page) { async function lockPage(page) {
const commitButton = page.locator('button:has-text("Commit Entries")'); const commitButton = page.locator('button:has-text("Commit Entries")');
await commitButton.click(); await commitButton.click();
//Wait until Lock Banner is visible //Wait until Lock Banner is visible
await page.locator('text=Lock Page').click(); await page.locator('text=Lock Page').click();
} }

View File

@ -29,243 +29,247 @@ const { createDomainObjectWithDefaults, selectInspectorTab } = require('../../..
const nbUtils = require('../../../../helper/notebookUtils'); const nbUtils = require('../../../../helper/notebookUtils');
/** /**
* Creates a notebook object and adds an entry. * Creates a notebook object and adds an entry.
* @param {import('@playwright/test').Page} - page to load * @param {import('@playwright/test').Page} - page to load
* @param {number} [iterations = 1] - the number of entries to create * @param {number} [iterations = 1] - the number of entries to create
*/ */
async function createNotebookAndEntry(page, iterations = 1) { async function createNotebookAndEntry(page, iterations = 1) {
const notebook = createDomainObjectWithDefaults(page, { type: 'Notebook' }); const notebook = createDomainObjectWithDefaults(page, { type: 'Notebook' });
for (let iteration = 0; iteration < iterations; iteration++) { for (let iteration = 0; iteration < iterations; iteration++) {
await nbUtils.enterTextEntry(page, `Entry ${iteration}`); await nbUtils.enterTextEntry(page, `Entry ${iteration}`);
} }
return notebook; return notebook;
} }
/** /**
* Creates a notebook object, adds an entry, and adds a tag. * Creates a notebook object, adds an entry, and adds a tag.
* @param {import('@playwright/test').Page} page * @param {import('@playwright/test').Page} page
* @param {number} [iterations = 1] - the number of entries (and tags) to create * @param {number} [iterations = 1] - the number of entries (and tags) to create
*/ */
async function createNotebookEntryAndTags(page, iterations = 1) { async function createNotebookEntryAndTags(page, iterations = 1) {
const notebook = await createNotebookAndEntry(page, iterations); const notebook = await createNotebookAndEntry(page, iterations);
await selectInspectorTab(page, 'Annotations'); await selectInspectorTab(page, 'Annotations');
for (let iteration = 0; iteration < iterations; iteration++) { for (let iteration = 0; iteration < iterations; iteration++) {
// Hover and click "Add Tag" button // Hover and click "Add Tag" button
// Hover is needed here to "slow down" the actions while running in headless mode // Hover is needed here to "slow down" the actions while running in headless mode
await page.locator(`[aria-label="Notebook Entry"] >> nth = ${iteration}`).click(); await page.locator(`[aria-label="Notebook Entry"] >> nth = ${iteration}`).click();
await page.hover(`button:has-text("Add Tag")`); await page.hover(`button:has-text("Add Tag")`);
await page.locator(`button:has-text("Add Tag")`).click(); await page.locator(`button:has-text("Add Tag")`).click();
// Click inside the tag search input // Click inside the tag search input
await page.locator('[placeholder="Type to select tag"]').click(); await page.locator('[placeholder="Type to select tag"]').click();
// Select the "Driving" tag // Select the "Driving" tag
await page.locator('[aria-label="Autocomplete Options"] >> text=Driving').click(); await page.locator('[aria-label="Autocomplete Options"] >> text=Driving').click();
// Hover and click "Add Tag" button // Hover and click "Add Tag" button
// Hover is needed here to "slow down" the actions while running in headless mode // Hover is needed here to "slow down" the actions while running in headless mode
await page.hover(`button:has-text("Add Tag")`); await page.hover(`button:has-text("Add Tag")`);
await page.locator(`button:has-text("Add Tag")`).click(); await page.locator(`button:has-text("Add Tag")`).click();
// Click inside the tag search input // Click inside the tag search input
await page.locator('[placeholder="Type to select tag"]').click(); await page.locator('[placeholder="Type to select tag"]').click();
// Select the "Science" tag // Select the "Science" tag
await page.locator('[aria-label="Autocomplete Options"] >> text=Science').click(); await page.locator('[aria-label="Autocomplete Options"] >> text=Science').click();
} }
return notebook; return notebook;
} }
test.describe('Tagging in Notebooks @addInit', () => { test.describe('Tagging in Notebooks @addInit', () => {
test.beforeEach(async ({ page }) => { test.beforeEach(async ({ page }) => {
//Go to baseURL //Go to baseURL
await page.goto('./', { waitUntil: 'domcontentloaded' }); await page.goto('./', { waitUntil: 'domcontentloaded' });
}); });
test('Can load tags', async ({ page }) => { test('Can load tags', async ({ page }) => {
await createNotebookAndEntry(page); await createNotebookAndEntry(page);
await selectInspectorTab(page, 'Annotations'); await selectInspectorTab(page, 'Annotations');
await page.locator('button:has-text("Add Tag")').click(); await page.locator('button:has-text("Add Tag")').click();
await page.locator('[placeholder="Type to select tag"]').click(); await page.locator('[placeholder="Type to select tag"]').click();
await expect(page.locator('[aria-label="Autocomplete Options"]')).toContainText("Science"); await expect(page.locator('[aria-label="Autocomplete Options"]')).toContainText('Science');
await expect(page.locator('[aria-label="Autocomplete Options"]')).toContainText("Drilling"); await expect(page.locator('[aria-label="Autocomplete Options"]')).toContainText('Drilling');
await expect(page.locator('[aria-label="Autocomplete Options"]')).toContainText("Driving"); await expect(page.locator('[aria-label="Autocomplete Options"]')).toContainText('Driving');
}); });
test('Can add tags', async ({ page }) => { test('Can add tags', async ({ page }) => {
await createNotebookEntryAndTags(page); await createNotebookEntryAndTags(page);
await expect(page.locator('[aria-label="Notebook Entry"]')).toContainText("Science"); await expect(page.locator('[aria-label="Notebook Entry"]')).toContainText('Science');
await expect(page.locator('[aria-label="Notebook Entry"]')).toContainText("Driving"); await expect(page.locator('[aria-label="Notebook Entry"]')).toContainText('Driving');
await page.locator('button:has-text("Add Tag")').click(); await page.locator('button:has-text("Add Tag")').click();
await page.locator('[placeholder="Type to select tag"]').click(); await page.locator('[placeholder="Type to select tag"]').click();
await expect(page.locator('[aria-label="Autocomplete Options"]')).not.toContainText("Science"); await expect(page.locator('[aria-label="Autocomplete Options"]')).not.toContainText('Science');
await expect(page.locator('[aria-label="Autocomplete Options"]')).not.toContainText("Driving"); await expect(page.locator('[aria-label="Autocomplete Options"]')).not.toContainText('Driving');
await expect(page.locator('[aria-label="Autocomplete Options"]')).toContainText("Drilling"); await expect(page.locator('[aria-label="Autocomplete Options"]')).toContainText('Drilling');
}); });
test('Can add tags with blank entry', async ({ page }) => { test('Can add tags with blank entry', async ({ page }) => {
await createDomainObjectWithDefaults(page, { type: 'Notebook' }); await createDomainObjectWithDefaults(page, { type: 'Notebook' });
await selectInspectorTab(page, 'Annotations'); await selectInspectorTab(page, 'Annotations');
await nbUtils.enterTextEntry(page, ''); await nbUtils.enterTextEntry(page, '');
await page.hover(`button:has-text("Add Tag")`); await page.hover(`button:has-text("Add Tag")`);
await page.locator(`button:has-text("Add Tag")`).click(); await page.locator(`button:has-text("Add Tag")`).click();
// Click inside the tag search input // Click inside the tag search input
await page.locator('[placeholder="Type to select tag"]').click(); await page.locator('[placeholder="Type to select tag"]').click();
// Select the "Driving" tag // Select the "Driving" tag
await page.locator('[aria-label="Autocomplete Options"] >> text=Driving').click(); await page.locator('[aria-label="Autocomplete Options"] >> text=Driving').click();
await expect(page.locator('[aria-label="Notebook Entry"]')).toContainText("Driving"); await expect(page.locator('[aria-label="Notebook Entry"]')).toContainText('Driving');
}); });
test('Can cancel adding tags', async ({ page }) => { test('Can cancel adding tags', async ({ page }) => {
await createNotebookAndEntry(page); await createNotebookAndEntry(page);
await selectInspectorTab(page, 'Annotations'); await selectInspectorTab(page, 'Annotations');
// Test canceling adding a tag after we click "Type to select tag" // Test canceling adding a tag after we click "Type to select tag"
await page.locator('button:has-text("Add Tag")').click(); await page.locator('button:has-text("Add Tag")').click();
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.locator('[aria-label="OpenMCT Search"] input[type="search"]').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.locator('[aria-label="OpenMCT Search"] input[type="search"]').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.locator('[aria-label="OpenMCT Search"] input[type="search"]').click();
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"]')).toContainText("Science"); await expect(page.locator('[aria-label="Search Result"]')).toContainText('Science');
await expect(page.locator('[aria-label="Search Result"]')).not.toContainText("Driving"); await expect(page.locator('[aria-label="Search Result"]')).not.toContainText('Driving');
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('Sc'); await page.locator('[aria-label="OpenMCT Search"] input[type="search"]').fill('Sc');
await expect(page.locator('[aria-label="Search Result"]')).toContainText("Science"); await expect(page.locator('[aria-label="Search Result"]')).toContainText('Science');
await expect(page.locator('[aria-label="Search Result"]')).not.toContainText("Driving"); await expect(page.locator('[aria-label="Search Result"]')).not.toContainText('Driving');
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.locator('text=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
await page.locator('button[title="Edit"]').click();
await page.locator('[aria-label="OpenMCT Search"] input[type="search"]').click();
await page.locator('[aria-label="OpenMCT Search"] input[type="search"]').fill('Sc');
await expect(page.locator('[aria-label="Search Result"]')).toContainText("Science");
await page.getByText('Entry 0').click();
await expect(page.locator('.js-preview-window')).toBeVisible();
}); });
test('Can delete tags', async ({ page }) => { // Go back into edit mode for the display layout
await createNotebookEntryAndTags(page); await page.locator('button[title="Edit"]').click();
// Delete Driving
await page.hover('[aria-label="Tag"]:has-text("Driving")');
await page.locator('[aria-label="Remove tag Driving"]').click();
await expect(page.locator('[aria-label="Tags Inspector"]')).toContainText("Science"); await page.locator('[aria-label="OpenMCT Search"] input[type="search"]').click();
await expect(page.locator('[aria-label="Tags Inspector"]')).not.toContainText("Driving"); await page.locator('[aria-label="OpenMCT Search"] input[type="search"]').fill('Sc');
await expect(page.locator('[aria-label="Search Result"]')).toContainText('Science');
await page.getByText('Entry 0').click();
await expect(page.locator('.js-preview-window')).toBeVisible();
});
await page.locator('[aria-label="OpenMCT Search"] input[type="search"]').fill('sc'); test('Can delete tags', async ({ page }) => {
await expect(page.locator('[aria-label="Search Result"]')).not.toContainText("Driving"); await createNotebookEntryAndTags(page);
// Delete Driving
await page.hover('[aria-label="Tag"]:has-text("Driving")');
await page.locator('[aria-label="Remove tag Driving"]').click();
await expect(page.locator('[aria-label="Tags Inspector"]')).toContainText('Science');
await expect(page.locator('[aria-label="Tags Inspector"]')).not.toContainText('Driving');
await page.locator('[aria-label="OpenMCT Search"] input[type="search"]').fill('sc');
await expect(page.locator('[aria-label="Search Result"]')).not.toContainText('Driving');
});
test('Can delete entries without tags', async ({ page }) => {
test.info().annotations.push({
type: 'issue',
description: 'https://github.com/nasa/openmct/issues/5823'
}); });
test('Can delete entries without tags', async ({ page }) => { await createNotebookEntryAndTags(page);
test.info().annotations.push({
type: 'issue',
description: 'https://github.com/nasa/openmct/issues/5823'
});
await createNotebookEntryAndTags(page); await page.locator('text=To start a new entry, click here or drag and drop any object').click();
const entryLocator = `[aria-label="Notebook Entry Input"] >> nth = 1`;
await page.locator(entryLocator).click();
await page.locator(entryLocator).fill(`An entry without tags`);
await page.locator('[aria-label="Notebook Entry Input"] >> nth=1').press('Enter');
await page.locator('text=To start a new entry, click here or drag and drop any object').click(); await page.hover('[aria-label="Notebook Entry Input"] >> nth=1');
const entryLocator = `[aria-label="Notebook Entry Input"] >> nth = 1`; await page.locator('button[title="Delete this entry"]').last().click();
await page.locator(entryLocator).click(); await expect(
await page.locator(entryLocator).fill(`An entry without tags`); page.locator('text=This action will permanently delete this entry. Do you wish to continue?')
await page.locator('[aria-label="Notebook Entry Input"] >> nth=1').press('Enter'); ).toBeVisible();
await page.locator('button:has-text("Ok")').click();
await expect(
page.locator('text=This action will permanently delete this entry. Do you wish to continue?')
).toBeHidden();
});
await page.hover('[aria-label="Notebook Entry Input"] >> nth=1'); test('Can delete objects with tags and neither return in search', async ({ page }) => {
await page.locator('button[title="Delete this entry"]').last().click(); await createNotebookEntryAndTags(page);
await expect(page.locator('text=This action will permanently delete this entry. Do you wish to continue?')).toBeVisible(); // Delete Notebook
await page.locator('button:has-text("Ok")').click(); await page.locator('button[title="More options"]').click();
await expect(page.locator('text=This action will permanently delete this entry. Do you wish to continue?')).toBeHidden(); await page.locator('li[title="Remove this object from its containing object."]').click();
}); await page.locator('button:has-text("OK")').click();
await page.goto('./', { waitUntil: 'domcontentloaded' });
test('Can delete objects with tags and neither return in search', async ({ page }) => { await page.locator('[aria-label="OpenMCT Search"] input[type="search"]').fill('Unnamed');
await createNotebookEntryAndTags(page); await expect(page.locator('text=No results found')).toBeVisible();
// Delete Notebook await page.locator('[aria-label="OpenMCT Search"] input[type="search"]').fill('sci');
await page.locator('button[title="More options"]').click(); await expect(page.locator('text=No results found')).toBeVisible();
await page.locator('li[title="Remove this object from its containing object."]').click(); await page.locator('[aria-label="OpenMCT Search"] input[type="search"]').fill('dri');
await page.locator('button:has-text("OK")').click(); await expect(page.locator('text=No results found')).toBeVisible();
await page.goto('./', { waitUntil: 'domcontentloaded' }); });
test('Tags persist across reload', async ({ page }) => {
//Go to baseURL
await page.goto('./', { waitUntil: 'domcontentloaded' });
await page.locator('[aria-label="OpenMCT Search"] input[type="search"]').fill('Unnamed'); const ITERATIONS = 4;
await expect(page.locator('text=No results found')).toBeVisible(); const notebook = await createNotebookEntryAndTags(page, ITERATIONS);
await page.locator('[aria-label="OpenMCT Search"] input[type="search"]').fill('sci'); await page.goto(notebook.url);
await expect(page.locator('text=No results found')).toBeVisible();
await page.locator('[aria-label="OpenMCT Search"] input[type="search"]').fill('dri');
await expect(page.locator('text=No results found')).toBeVisible();
});
test('Tags persist across reload', async ({ page }) => {
//Go to baseURL
await page.goto('./', { waitUntil: 'domcontentloaded' });
const ITERATIONS = 4; // Verify tags are present
const notebook = await createNotebookEntryAndTags(page, ITERATIONS); for (let iteration = 0; iteration < ITERATIONS; iteration++) {
await page.goto(notebook.url); const entryLocator = `[aria-label="Notebook Entry"] >> nth = ${iteration}`;
await expect(page.locator(entryLocator)).toContainText('Science');
await expect(page.locator(entryLocator)).toContainText('Driving');
}
// Verify tags are present //Reload Page
for (let iteration = 0; iteration < ITERATIONS; iteration++) { await page.reload({ waitUntil: 'domcontentloaded' });
const entryLocator = `[aria-label="Notebook Entry"] >> nth = ${iteration}`;
await expect(page.locator(entryLocator)).toContainText("Science");
await expect(page.locator(entryLocator)).toContainText("Driving");
}
//Reload Page // Verify tags persist across reload
await page.reload({ waitUntil: 'domcontentloaded' }); for (let iteration = 0; iteration < ITERATIONS; iteration++) {
const entryLocator = `[aria-label="Notebook Entry"] >> nth = ${iteration}`;
await expect(page.locator(entryLocator)).toContainText('Science');
await expect(page.locator(entryLocator)).toContainText('Driving');
}
});
test('Can cancel adding a tag', async ({ page }) => {
await createNotebookAndEntry(page);
// Verify tags persist across reload await selectInspectorTab(page, 'Annotations');
for (let iteration = 0; iteration < ITERATIONS; iteration++) {
const entryLocator = `[aria-label="Notebook Entry"] >> nth = ${iteration}`;
await expect(page.locator(entryLocator)).toContainText("Science");
await expect(page.locator(entryLocator)).toContainText("Driving");
}
});
test('Can cancel adding a tag', async ({ page }) => {
await createNotebookAndEntry(page);
await selectInspectorTab(page, 'Annotations'); // Click on the "Add Tag" button
await page.locator('button:has-text("Add Tag")').click();
// Click on the "Add Tag" button // Click inside the AutoComplete field
await page.locator('button:has-text("Add Tag")').click(); await page.locator('[placeholder="Type to select tag"]').click();
// Click inside the AutoComplete field // Click on the "Tags" header (simulating a click outside the autocomplete)
await page.locator('[placeholder="Type to select tag"]').click(); await page.locator('div.c-inspect-properties__header:has-text("Tags")').click();
// Click on the "Tags" header (simulating a click outside the autocomplete) // Verify there is a button with text "Add Tag"
await page.locator('div.c-inspect-properties__header:has-text("Tags")').click(); await expect(page.locator('button:has-text("Add Tag")')).toBeVisible();
// Verify there is a button with text "Add Tag" // Verify the AutoComplete field is hidden
await expect(page.locator('button:has-text("Add Tag")')).toBeVisible(); await expect(page.locator('[placeholder="Type to select tag"]')).toBeHidden();
});
// Verify the AutoComplete field is hidden
await expect(page.locator('[placeholder="Type to select tag"]')).toBeHidden();
});
}); });

View File

@ -21,8 +21,8 @@
*****************************************************************************/ *****************************************************************************/
/* global __dirname */ /* 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'); const path = require('path');
const { test, expect } = require('../../../../pluginFixtures'); const { test, expect } = require('../../../../pluginFixtures');
@ -38,117 +38,120 @@ STUB (test.fixme) Rolling through each
*/ */
test.describe('Operator Status', () => { 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({ path: path.join(__dirname, '../../../../helper/', 'addInitExampleUser.js')}); await page.addInitScript({
await page.addInitScript({ path: path.join(__dirname, '../../../../helper/', 'addInitOperatorStatus.js')}); path: path.join(__dirname, '../../../../helper/', 'addInitExampleUser.js')
await page.goto('./', { waitUntil: 'domcontentloaded' });
}); });
await page.addInitScript({
// verify that operator status is visible path: path.join(__dirname, '../../../../helper/', 'addInitOperatorStatus.js')
test('operator status is visible and expands when clicked', async ({ page }) => {
await expect(page.locator('div[title="Set my operator status"]')).toBeVisible();
await page.locator('div[title="Set my operator status"]').click();
// expect default status to be 'GO'
await expect(page.locator('.c-status-poll-panel')).toBeVisible();
}); });
await page.goto('./', { waitUntil: 'domcontentloaded' });
});
test('poll question indicator remains when blank poll set', async ({ page }) => { // verify that operator status is visible
await expect(page.locator('div[title="Set the current poll question"]')).toBeVisible(); test('operator status is visible and expands when clicked', async ({ page }) => {
await page.locator('div[title="Set the current poll question"]').click(); await expect(page.locator('div[title="Set my operator status"]')).toBeVisible();
// set to blank await page.locator('div[title="Set my operator status"]').click();
await page.getByRole('button', { name: 'Update' }).click();
// should still be visible // expect default status to be 'GO'
await expect(page.locator('div[title="Set the current poll question"]')).toBeVisible(); await expect(page.locator('.c-status-poll-panel')).toBeVisible();
}); });
// Verify that user 1 sees updates from user/role 2 (Not possible without openmct-yamcs implementation) test('poll question indicator remains when blank poll set', async ({ page }) => {
test('operator status table reflects answered values', async ({ page }) => { await expect(page.locator('div[title="Set the current poll question"]')).toBeVisible();
// user navigates to operator status poll await page.locator('div[title="Set the current poll question"]').click();
const statusPollIndicator = page.locator('div[title="Set my operator status"]'); // set to blank
await statusPollIndicator.click(); await page.getByRole('button', { name: 'Update' }).click();
// get user role value // should still be visible
const userRole = page.locator('.c-status-poll-panel__user-role'); await expect(page.locator('div[title="Set the current poll question"]')).toBeVisible();
const userRoleText = await userRole.innerText(); });
// get selected status value // Verify that user 1 sees updates from user/role 2 (Not possible without openmct-yamcs implementation)
const selectStatus = page.locator('select[name="setStatus"]'); test('operator status table reflects answered values', async ({ page }) => {
await selectStatus.selectOption({ index: 1}); // user navigates to operator status poll
const initialStatusValue = await selectStatus.inputValue(); const statusPollIndicator = page.locator('div[title="Set my operator status"]');
await statusPollIndicator.click();
// open manage status poll // get user role value
const manageStatusPollIndicator = page.locator('div[title="Set the current poll question"]'); const userRole = page.locator('.c-status-poll-panel__user-role');
await manageStatusPollIndicator.click(); const userRoleText = await userRole.innerText();
// parse the table row values
const row = page.locator(`tr:has-text("${userRoleText}")`);
const rowValues = await row.innerText();
const rowValuesArr = rowValues.split('\t');
const COLUMN_STATUS_INDEX = 1;
// check initial set value matches status table
expect(rowValuesArr[COLUMN_STATUS_INDEX].toLowerCase())
.toEqual(initialStatusValue.toLowerCase());
// change user status // get selected status value
await statusPollIndicator.click(); const selectStatus = page.locator('select[name="setStatus"]');
// FIXME: might want to grab a dynamic option instead of arbitrary await selectStatus.selectOption({ index: 1 });
await page.locator('select[name="setStatus"]').selectOption({ index: 2}); const initialStatusValue = await selectStatus.inputValue();
const updatedStatusValue = await selectStatus.inputValue();
// verify user status is reflected in table
await manageStatusPollIndicator.click();
const updatedRow = page.locator(`tr:has-text("${userRoleText}")`); // open manage status poll
const updatedRowValues = await updatedRow.innerText(); const manageStatusPollIndicator = page.locator('div[title="Set the current poll question"]');
const updatedRowValuesArr = updatedRowValues.split('\t'); await manageStatusPollIndicator.click();
// parse the table row values
const row = page.locator(`tr:has-text("${userRoleText}")`);
const rowValues = await row.innerText();
const rowValuesArr = rowValues.split('\t');
const COLUMN_STATUS_INDEX = 1;
// check initial set value matches status table
expect(rowValuesArr[COLUMN_STATUS_INDEX].toLowerCase()).toEqual(
initialStatusValue.toLowerCase()
);
expect(updatedRowValuesArr[COLUMN_STATUS_INDEX].toLowerCase()) // change user status
.toEqual(updatedStatusValue.toLowerCase()); await statusPollIndicator.click();
// FIXME: might want to grab a dynamic option instead of arbitrary
await page.locator('select[name="setStatus"]').selectOption({ index: 2 });
const updatedStatusValue = await selectStatus.inputValue();
// verify user status is reflected in table
await manageStatusPollIndicator.click();
}); const updatedRow = page.locator(`tr:has-text("${userRoleText}")`);
const updatedRowValues = await updatedRow.innerText();
const updatedRowValuesArr = updatedRowValues.split('\t');
test('clear poll button removes poll responses', async ({ page }) => { expect(updatedRowValuesArr[COLUMN_STATUS_INDEX].toLowerCase()).toEqual(
// user navigates to operator status poll updatedStatusValue.toLowerCase()
const statusPollIndicator = page.locator('div[title="Set my operator status"]'); );
await statusPollIndicator.click(); });
// get user role value test('clear poll button removes poll responses', async ({ page }) => {
const userRole = page.locator('.c-status-poll-panel__user-role'); // user navigates to operator status poll
const userRoleText = await userRole.innerText(); const statusPollIndicator = page.locator('div[title="Set my operator status"]');
await statusPollIndicator.click();
// get selected status value // get user role value
const selectStatus = page.locator('select[name="setStatus"]'); const userRole = page.locator('.c-status-poll-panel__user-role');
// FIXME: might want to grab a dynamic option instead of arbitrary const userRoleText = await userRole.innerText();
await selectStatus.selectOption({ index: 1});
const initialStatusValue = await selectStatus.inputValue();
// open manage status poll // get selected status value
const manageStatusPollIndicator = page.locator('div[title="Set the current poll question"]'); const selectStatus = page.locator('select[name="setStatus"]');
await manageStatusPollIndicator.click(); // FIXME: might want to grab a dynamic option instead of arbitrary
// parse the table row values await selectStatus.selectOption({ index: 1 });
const row = page.locator(`tr:has-text("${userRoleText}")`); const initialStatusValue = await selectStatus.inputValue();
const rowValues = await row.innerText();
const rowValuesArr = rowValues.split('\t');
const COLUMN_STATUS_INDEX = 1;
// check initial set value matches status table
expect(rowValuesArr[COLUMN_STATUS_INDEX].toLowerCase())
.toEqual(initialStatusValue.toLowerCase());
// clear the poll // open manage status poll
await page.locator('button[title="Clear the previous poll question"]').click(); const manageStatusPollIndicator = page.locator('div[title="Set the current poll question"]');
await manageStatusPollIndicator.click();
// parse the table row values
const row = page.locator(`tr:has-text("${userRoleText}")`);
const rowValues = await row.innerText();
const rowValuesArr = rowValues.split('\t');
const COLUMN_STATUS_INDEX = 1;
// check initial set value matches status table
expect(rowValuesArr[COLUMN_STATUS_INDEX].toLowerCase()).toEqual(
initialStatusValue.toLowerCase()
);
const updatedRow = page.locator(`tr:has-text("${userRoleText}")`); // clear the poll
const updatedRowValues = await updatedRow.innerText(); await page.locator('button[title="Clear the previous poll question"]').click();
const updatedRowValuesArr = updatedRowValues.split('\t');
const UNSET_VALUE_LABEL = 'Not set';
expect(updatedRowValuesArr[COLUMN_STATUS_INDEX])
.toEqual(UNSET_VALUE_LABEL);
}); const updatedRow = page.locator(`tr:has-text("${userRoleText}")`);
const updatedRowValues = await updatedRow.innerText();
test.fixme('iterate through all possible response values', async ({ page }) => { const updatedRowValuesArr = updatedRowValues.split('\t');
// test all possible respone values for the poll const UNSET_VALUE_LABEL = 'Not set';
}); expect(updatedRowValuesArr[COLUMN_STATUS_INDEX]).toEqual(UNSET_VALUE_LABEL);
});
test.fixme('iterate through all possible response values', async ({ page }) => {
// test all possible respone values for the poll
});
}); });

View File

@ -27,81 +27,95 @@ Testsuite for plot autoscale.
const { selectInspectorTab } = require('../../../../appActions'); const { selectInspectorTab } = require('../../../../appActions');
const { test, expect } = require('../../../../pluginFixtures'); const { test, expect } = require('../../../../pluginFixtures');
test.use({ test.use({
viewport: { viewport: {
width: 1280, width: 1280,
height: 720 height: 720
} }
}); });
test.describe('Autoscale', () => { test.describe('Autoscale', () => {
test('User can set autoscale with a valid range @snapshot', async ({ page, openmctConfig }) => { test('User can set autoscale with a valid range @snapshot', async ({ page, openmctConfig }) => {
const { myItemsFolderName } = openmctConfig; const { myItemsFolderName } = openmctConfig;
//This is necessary due to the size of the test suite. //This is necessary due to the size of the test suite.
test.slow(); test.slow();
await page.goto('./', { waitUntil: 'domcontentloaded' }); await page.goto('./', { waitUntil: 'domcontentloaded' });
await setTimeRange(page); await setTimeRange(page);
await createSinewaveOverlayPlot(page, myItemsFolderName); await createSinewaveOverlayPlot(page, myItemsFolderName);
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.click('button[title="Edit"]');
await selectInspectorTab(page, 'Config'); await selectInspectorTab(page, 'Config');
await turnOffAutoscale(page); await turnOffAutoscale(page);
await setUserDefinedMinAndMax(page, '-2', '2'); await setUserDefinedMinAndMax(page, '-2', '2');
// save // save
await page.click('button[title="Save"]'); await page.click('button[title="Save"]');
await Promise.all([ await Promise.all([
page.locator('li[title = "Save and Finish Editing"]').click(), page.locator('li[title = "Save and Finish Editing"]').click(),
//Wait for Save Banner to appear //Wait for Save Banner to appear
page.waitForSelector('.c-message-banner__message') page.waitForSelector('.c-message-banner__message')
]); ]);
//Wait until Save Banner is gone //Wait until Save Banner is gone
await page.locator('.c-message-banner__close-button').click(); await page.locator('.c-message-banner__close-button').click();
await page.waitForSelector('.c-message-banner__message', { state: 'detached'}); await page.waitForSelector('.c-message-banner__message', { state: 'detached' });
// Make sure that after turning off autoscale, the user entered range values are reflexted in the ticks. // Make sure that after turning off autoscale, the user entered range values are reflexted in the ticks.
await testYTicks(page, ['-2.00', '-1.50', '-1.00', '-0.50', '0.00', '0.50', '1.00', '1.50', '2.00']); await testYTicks(page, [
'-2.00',
'-1.50',
'-1.00',
'-0.50',
'0.00',
'0.50',
'1.00',
'1.50',
'2.00'
]);
const canvas = page.locator('canvas').nth(1); const canvas = page.locator('canvas').nth(1);
await canvas.hover({trial: true}); await canvas.hover({ trial: true });
await expect(page.locator('.js-series-data-loaded')).toBeVisible(); await expect(page.locator('.js-series-data-loaded')).toBeVisible();
expect.soft(await canvas.screenshot()).toMatchSnapshot('autoscale-canvas-prepan.png', { animations: 'disabled' }); expect
.soft(await canvas.screenshot())
.toMatchSnapshot('autoscale-canvas-prepan.png', { animations: 'disabled' });
//Alt Drag Start //Alt Drag Start
await page.keyboard.down('Alt'); await page.keyboard.down('Alt');
await canvas.dragTo(canvas, { await canvas.dragTo(canvas, {
sourcePosition: { sourcePosition: {
x: 200, x: 200,
y: 200 y: 200
}, },
targetPosition: { targetPosition: {
x: 400, x: 400,
y: 400 y: 400
} }
});
//Alt Drag End
await page.keyboard.up('Alt');
// Ensure the drag worked.
await testYTicks(page, ['0.00', '0.50', '1.00', '1.50', '2.00', '2.50', '3.00', '3.50']);
//Wait for canvas to stablize.
await canvas.hover({trial: true});
expect.soft(await canvas.screenshot()).toMatchSnapshot('autoscale-canvas-panned.png', { animations: 'disabled' });
}); });
//Alt Drag End
await page.keyboard.up('Alt');
// Ensure the drag worked.
await testYTicks(page, ['0.00', '0.50', '1.00', '1.50', '2.00', '2.50', '3.00', '3.50']);
//Wait for canvas to stablize.
await canvas.hover({ trial: true });
expect
.soft(await canvas.screenshot())
.toMatchSnapshot('autoscale-canvas-panned.png', { animations: 'disabled' });
});
}); });
/** /**
@ -109,16 +123,20 @@ test.describe('Autoscale', () => {
* @param {string} start * @param {string} start
* @param {string} end * @param {string} end
*/ */
async function setTimeRange(page, start = '2022-03-29 22:00:00.000Z', end = '2022-03-29 22:00:30.000Z') { async function setTimeRange(
// Set a specific time range for consistency, otherwise it will change page,
// on every test to a range based on the current time. start = '2022-03-29 22:00:00.000Z',
end = '2022-03-29 22:00:30.000Z'
) {
// Set a specific time range for consistency, otherwise it will change
// on every test to a range based on the current time.
const timeInputs = page.locator('input.c-input--datetime'); const timeInputs = page.locator('input.c-input--datetime');
await timeInputs.first().click(); await timeInputs.first().click();
await timeInputs.first().fill(start); await timeInputs.first().fill(start);
await timeInputs.nth(1).click(); await timeInputs.nth(1).click();
await timeInputs.nth(1).fill(end); await timeInputs.nth(1).fill(end);
} }
/** /**
@ -126,54 +144,57 @@ async function setTimeRange(page, start = '2022-03-29 22:00:00.000Z', end = '202
* @param {string} myItemsFolderName * @param {string} myItemsFolderName
*/ */
async function createSinewaveOverlayPlot(page, myItemsFolderName) { async function createSinewaveOverlayPlot(page, myItemsFolderName) {
// click create button // click create button
await page.locator('button:has-text("Create")').click(); await page.locator('button:has-text("Create")').click();
// add overlay plot with defaults // add overlay plot with defaults
await page.locator('li[role="menuitem"]:has-text("Overlay Plot")').click(); await page.locator('li[role="menuitem"]:has-text("Overlay Plot")').click();
await Promise.all([ await Promise.all([
page.waitForNavigation(), page.waitForNavigation(),
page.locator('button:has-text("OK")').click(), page.locator('button:has-text("OK")').click(),
//Wait for Save Banner to appear1 //Wait for Save Banner to appear1
page.waitForSelector('.c-message-banner__message') page.waitForSelector('.c-message-banner__message')
]); ]);
//Wait until Save Banner is gone //Wait until Save Banner is gone
await page.locator('.c-message-banner__close-button').click(); await page.locator('.c-message-banner__close-button').click();
await page.waitForSelector('.c-message-banner__message', { state: 'detached'}); await page.waitForSelector('.c-message-banner__message', { state: 'detached' });
// save (exit edit mode) // save (exit edit mode)
await page.locator('text=Snapshot Save and Finish Editing Save and Continue Editing >> button').nth(1).click(); await page
await page.locator('text=Save and Finish Editing').click(); .locator('text=Snapshot Save and Finish Editing Save and Continue Editing >> button')
.nth(1)
.click();
await page.locator('text=Save and Finish Editing').click();
// click create button // click create button
await page.locator('button:has-text("Create")').click(); await page.locator('button:has-text("Create")').click();
// add sine wave generator with defaults // add sine wave generator with defaults
await page.locator('li[role="menuitem"]:has-text("Sine Wave Generator")').click(); await page.locator('li[role="menuitem"]:has-text("Sine Wave Generator")').click();
await Promise.all([ await Promise.all([
page.waitForNavigation(), page.waitForNavigation(),
page.locator('button:has-text("OK")').click(), page.locator('button:has-text("OK")').click(),
//Wait for Save Banner to appear1 //Wait for Save Banner to appear1
page.waitForSelector('.c-message-banner__message') page.waitForSelector('.c-message-banner__message')
]); ]);
//Wait until Save Banner is gone //Wait until Save Banner is gone
await page.locator('.c-message-banner__close-button').click(); await page.locator('.c-message-banner__close-button').click();
await page.waitForSelector('.c-message-banner__message', { state: 'detached'}); await page.waitForSelector('.c-message-banner__message', { state: 'detached' });
// focus the overlay plot // focus the overlay plot
await page.locator(`text=Open MCT ${myItemsFolderName} >> span`).nth(3).click(); await page.locator(`text=Open MCT ${myItemsFolderName} >> span`).nth(3).click();
await Promise.all([ await Promise.all([
page.waitForNavigation(), page.waitForNavigation(),
page.locator('text=Unnamed Overlay Plot').first().click() page.locator('text=Unnamed Overlay Plot').first().click()
]); ]);
} }
/** /**
* @param {import('@playwright/test').Page} page * @param {import('@playwright/test').Page} page
*/ */
async function turnOffAutoscale(page) { async function turnOffAutoscale(page) {
// uncheck autoscale // uncheck autoscale
await page.getByRole('checkbox', { name: 'Auto scale' }).uncheck(); await page.getByRole('checkbox', { name: 'Auto scale' }).uncheck();
} }
/** /**
@ -182,23 +203,23 @@ async function turnOffAutoscale(page) {
* @param {string} max * @param {string} max
*/ */
async function setUserDefinedMinAndMax(page, min, max) { async function setUserDefinedMinAndMax(page, min, max) {
// set minimum value // set minimum value
await page.getByRole('spinbutton').first().fill(min); await page.getByRole('spinbutton').first().fill(min);
// set maximum value // set maximum value
await page.getByRole('spinbutton').nth(1).fill(max); await page.getByRole('spinbutton').nth(1).fill(max);
} }
/** /**
* @param {import('@playwright/test').Page} page * @param {import('@playwright/test').Page} page
*/ */
async function testYTicks(page, values) { async function testYTicks(page, values) {
const yTicks = page.locator('.gl-plot-y-tick-label'); const yTicks = page.locator('.gl-plot-y-tick-label');
await page.locator('canvas >> nth=1').hover(); await page.locator('canvas >> nth=1').hover();
let promises = [yTicks.count().then(c => expect(c).toBe(values.length))]; let promises = [yTicks.count().then((c) => expect(c).toBe(values.length))];
for (let i = 0, l = values.length; i < l; i += 1) { for (let i = 0, l = values.length; i < l; i += 1) {
promises.push(expect.soft(yTicks.nth(i)).toHaveText(values[i])); // eslint-disable-line promises.push(expect.soft(yTicks.nth(i)).toHaveText(values[i])); // eslint-disable-line
} }
await Promise.all(promises); await Promise.all(promises);
} }

View File

@ -29,44 +29,50 @@ const { test, expect } = require('../../../../pluginFixtures');
const { selectInspectorTab } = require('../../../../appActions'); const { selectInspectorTab } = require('../../../../appActions');
test.describe('Log plot tests', () => { test.describe('Log plot tests', () => {
test('Log Plot ticks are functionally correct in regular and log mode and after refresh', async ({ page, openmctConfig }) => { test('Log Plot ticks are functionally correct in regular and log mode and after refresh', async ({
const { myItemsFolderName } = openmctConfig; page,
openmctConfig
}) => {
const { myItemsFolderName } = openmctConfig;
//Test.slow decorator is currently broken. Needs to be fixed in https://github.com/nasa/openmct/issues/5374 //Test.slow decorator is currently broken. Needs to be fixed in https://github.com/nasa/openmct/issues/5374
test.slow(); test.slow();
await makeOverlayPlot(page, myItemsFolderName); await makeOverlayPlot(page, myItemsFolderName);
await testRegularTicks(page); await testRegularTicks(page);
await enableEditMode(page); await enableEditMode(page);
await selectInspectorTab(page, 'Config'); await selectInspectorTab(page, 'Config');
await enableLogMode(page); await enableLogMode(page);
await testLogTicks(page); await testLogTicks(page);
await disableLogMode(page); await disableLogMode(page);
await testRegularTicks(page); await testRegularTicks(page);
await enableLogMode(page); await enableLogMode(page);
await testLogTicks(page); await testLogTicks(page);
await saveOverlayPlot(page); await saveOverlayPlot(page);
await testLogTicks(page); await testLogTicks(page);
}); });
// Leaving test as 'TODO' for now. // Leaving test as 'TODO' for now.
// NOTE: Not eligible for community contributions. // NOTE: Not eligible for community contributions.
test.fixme('Verify that log mode option is reflected in import/export JSON', async ({ page, openmctConfig }) => { test.fixme(
const { myItemsFolderName } = openmctConfig; 'Verify that log mode option is reflected in import/export JSON',
async ({ page, openmctConfig }) => {
const { myItemsFolderName } = openmctConfig;
await makeOverlayPlot(page, myItemsFolderName); await makeOverlayPlot(page, myItemsFolderName);
await enableEditMode(page); await enableEditMode(page);
await enableLogMode(page); await enableLogMode(page);
await saveOverlayPlot(page); await saveOverlayPlot(page);
// TODO ...export, delete the overlay, then import it... // TODO ...export, delete the overlay, then import it...
//await testLogTicks(page); //await testLogTicks(page);
// TODO, the plot is slightly at different position that in the other test, so this fails. // TODO, the plot is slightly at different position that in the other test, so this fails.
// ...We can fix it by copying all steps from the first test... // ...We can fix it by copying all steps from the first test...
// await testLogPlotPixels(page); // await testLogPlotPixels(page);
}); }
);
}); });
/** /**
@ -75,146 +81,149 @@ test.describe('Log plot tests', () => {
* @param {string} myItemsFolderName * @param {string} myItemsFolderName
*/ */
async function makeOverlayPlot(page, myItemsFolderName) { async function makeOverlayPlot(page, myItemsFolderName) {
// fresh page with time range from 2022-03-29 22:00:00.000Z to 2022-03-29 22:00:30.000Z // fresh page with time range from 2022-03-29 22:00:00.000Z to 2022-03-29 22:00:30.000Z
await page.goto('./', { waitUntil: 'domcontentloaded' }); await page.goto('./', { waitUntil: 'domcontentloaded' });
// Set a specific time range for consistency, otherwise it will change // Set a specific time range for consistency, otherwise it will change
// on every test to a range based on the current time. // on every test to a range based on the current time.
const timeInputs = page.locator('input.c-input--datetime'); const timeInputs = page.locator('input.c-input--datetime');
await timeInputs.first().click(); await timeInputs.first().click();
await timeInputs.first().fill('2022-03-29 22:00:00.000Z'); await timeInputs.first().fill('2022-03-29 22:00:00.000Z');
await timeInputs.nth(1).click(); await timeInputs.nth(1).click();
await timeInputs.nth(1).fill('2022-03-29 22:00:30.000Z'); await timeInputs.nth(1).fill('2022-03-29 22:00:30.000Z');
// create overlay plot // create overlay plot
await page.locator('button.c-create-button').click(); await page.locator('button.c-create-button').click();
await page.locator('li[role="menuitem"]:has-text("Overlay Plot")').click(); await page.locator('li[role="menuitem"]:has-text("Overlay Plot")').click();
await Promise.all([ await Promise.all([
page.waitForNavigation({ waitUntil: 'networkidle'}), page.waitForNavigation({ waitUntil: 'networkidle' }),
page.locator('button:has-text("OK")').click(), page.locator('button:has-text("OK")').click(),
//Wait for Save Banner to appear //Wait for Save Banner to appear
page.waitForSelector('.c-message-banner__message') page.waitForSelector('.c-message-banner__message')
]); ]);
//Wait until Save Banner is gone //Wait until Save Banner is gone
await page.locator('.c-message-banner__close-button').click(); await page.locator('.c-message-banner__close-button').click();
await page.waitForSelector('.c-message-banner__message', { state: 'detached'}); await page.waitForSelector('.c-message-banner__message', { state: 'detached' });
// save the overlay plot // save the overlay plot
await saveOverlayPlot(page); await saveOverlayPlot(page);
// create a sinewave generator // create a sinewave generator
await page.locator('button.c-create-button').click(); await page.locator('button.c-create-button').click();
await page.locator('li[role="menuitem"]:has-text("Sine Wave Generator")').click(); await page.locator('li[role="menuitem"]:has-text("Sine Wave Generator")').click();
// set amplitude to 6, offset 4, period 2 // set amplitude to 6, offset 4, period 2
await page.locator('div:nth-child(5) .c-form-row__controls .form-control .field input').click(); await page.locator('div:nth-child(5) .c-form-row__controls .form-control .field input').click();
await page.locator('div:nth-child(5) .c-form-row__controls .form-control .field input').fill('6'); await page.locator('div:nth-child(5) .c-form-row__controls .form-control .field input').fill('6');
await page.locator('div:nth-child(6) .c-form-row__controls .form-control .field input').click(); await page.locator('div:nth-child(6) .c-form-row__controls .form-control .field input').click();
await page.locator('div:nth-child(6) .c-form-row__controls .form-control .field input').fill('4'); await page.locator('div:nth-child(6) .c-form-row__controls .form-control .field input').fill('4');
await page.locator('div:nth-child(7) .c-form-row__controls .form-control .field input').click(); await page.locator('div:nth-child(7) .c-form-row__controls .form-control .field input').click();
await page.locator('div:nth-child(7) .c-form-row__controls .form-control .field input').fill('2'); await page.locator('div:nth-child(7) .c-form-row__controls .form-control .field input').fill('2');
// Click OK to make generator // Click OK to make generator
await Promise.all([ await Promise.all([
page.waitForNavigation({ waitUntil: 'networkidle'}), page.waitForNavigation({ waitUntil: 'networkidle' }),
page.locator('button:has-text("OK")').click(), page.locator('button:has-text("OK")').click(),
//Wait for Save Banner to appear //Wait for Save Banner to appear
page.waitForSelector('.c-message-banner__message') page.waitForSelector('.c-message-banner__message')
]); ]);
//Wait until Save Banner is gone //Wait until Save Banner is gone
await page.locator('.c-message-banner__close-button').click(); await page.locator('.c-message-banner__close-button').click();
await page.waitForSelector('.c-message-banner__message', { state: 'detached'}); await page.waitForSelector('.c-message-banner__message', { state: 'detached' });
// click on overlay plot // click on overlay plot
await page.locator(`text=Open MCT ${myItemsFolderName} >> span`).nth(3).click(); await page.locator(`text=Open MCT ${myItemsFolderName} >> span`).nth(3).click();
await Promise.all([ await Promise.all([
page.waitForNavigation(), page.waitForNavigation(),
page.locator('text=Unnamed Overlay Plot').first().click() page.locator('text=Unnamed Overlay Plot').first().click()
]); ]);
} }
/** /**
* @param {import('@playwright/test').Page} page * @param {import('@playwright/test').Page} page
*/ */
async function testRegularTicks(page) { async function testRegularTicks(page) {
const yTicks = page.locator('.gl-plot-y-tick-label'); const yTicks = page.locator('.gl-plot-y-tick-label');
expect(await yTicks.count()).toBe(7); expect(await yTicks.count()).toBe(7);
await expect(yTicks.nth(0)).toHaveText('-2'); await expect(yTicks.nth(0)).toHaveText('-2');
await expect(yTicks.nth(1)).toHaveText('0'); await expect(yTicks.nth(1)).toHaveText('0');
await expect(yTicks.nth(2)).toHaveText('2'); await expect(yTicks.nth(2)).toHaveText('2');
await expect(yTicks.nth(3)).toHaveText('4'); await expect(yTicks.nth(3)).toHaveText('4');
await expect(yTicks.nth(4)).toHaveText('6'); await expect(yTicks.nth(4)).toHaveText('6');
await expect(yTicks.nth(5)).toHaveText('8'); await expect(yTicks.nth(5)).toHaveText('8');
await expect(yTicks.nth(6)).toHaveText('10'); await expect(yTicks.nth(6)).toHaveText('10');
} }
/** /**
* @param {import('@playwright/test').Page} page * @param {import('@playwright/test').Page} page
*/ */
async function testLogTicks(page) { async function testLogTicks(page) {
const yTicks = page.locator('.gl-plot-y-tick-label'); const yTicks = page.locator('.gl-plot-y-tick-label');
expect(await yTicks.count()).toBe(9); expect(await yTicks.count()).toBe(9);
await expect(yTicks.nth(0)).toHaveText('-2.98'); await expect(yTicks.nth(0)).toHaveText('-2.98');
await expect(yTicks.nth(1)).toHaveText('-1.51'); await expect(yTicks.nth(1)).toHaveText('-1.51');
await expect(yTicks.nth(2)).toHaveText('-0.58'); await expect(yTicks.nth(2)).toHaveText('-0.58');
await expect(yTicks.nth(3)).toHaveText('-0.00'); await expect(yTicks.nth(3)).toHaveText('-0.00');
await expect(yTicks.nth(4)).toHaveText('0.58'); await expect(yTicks.nth(4)).toHaveText('0.58');
await expect(yTicks.nth(5)).toHaveText('1.51'); await expect(yTicks.nth(5)).toHaveText('1.51');
await expect(yTicks.nth(6)).toHaveText('2.98'); await expect(yTicks.nth(6)).toHaveText('2.98');
await expect(yTicks.nth(7)).toHaveText('5.31'); await expect(yTicks.nth(7)).toHaveText('5.31');
await expect(yTicks.nth(8)).toHaveText('9.00'); await expect(yTicks.nth(8)).toHaveText('9.00');
} }
/** /**
* @param {import('@playwright/test').Page} page * @param {import('@playwright/test').Page} page
*/ */
async function enableEditMode(page) { async function enableEditMode(page) {
// turn on edit mode // turn on edit mode
await page.getByRole('button', { name: 'Edit' }).click(); await page.getByRole('button', { name: 'Edit' }).click();
await expect(page.getByRole('button', { name: 'Save' })).toBeVisible(); await expect(page.getByRole('button', { name: 'Save' })).toBeVisible();
} }
/** /**
* @param {import('@playwright/test').Page} page * @param {import('@playwright/test').Page} page
*/ */
async function enableLogMode(page) { async function enableLogMode(page) {
await expect(page.getByRole('checkbox', { name: 'Log mode' })).not.toBeChecked(); await expect(page.getByRole('checkbox', { name: 'Log mode' })).not.toBeChecked();
await page.getByRole('checkbox', { name: 'Log mode' }).check(); await page.getByRole('checkbox', { name: 'Log mode' }).check();
} }
/** /**
* @param {import('@playwright/test').Page} page * @param {import('@playwright/test').Page} page
*/ */
async function disableLogMode(page) { async function disableLogMode(page) {
await expect(page.getByRole('checkbox', { name: 'Log mode' })).toBeChecked(); await expect(page.getByRole('checkbox', { name: 'Log mode' })).toBeChecked();
await page.getByRole('checkbox', { name: 'Log mode' }).uncheck(); await page.getByRole('checkbox', { name: 'Log mode' }).uncheck();
} }
/** /**
* @param {import('@playwright/test').Page} page * @param {import('@playwright/test').Page} page
*/ */
async function saveOverlayPlot(page) { async function saveOverlayPlot(page) {
// save overlay plot // save overlay plot
await page.locator('text=Snapshot Save and Finish Editing Save and Continue Editing >> button').nth(1).click(); await page
.locator('text=Snapshot Save and Finish Editing Save and Continue Editing >> button')
.nth(1)
.click();
await Promise.all([ await Promise.all([
page.locator('text=Save and Finish Editing').click(), page.locator('text=Save and Finish Editing').click(),
//Wait for Save Banner to appear //Wait for Save Banner to appear
page.waitForSelector('.c-message-banner__message') page.waitForSelector('.c-message-banner__message')
]); ]);
//Wait until Save Banner is gone //Wait until Save Banner is gone
await page.locator('.c-message-banner__close-button').click(); await page.locator('.c-message-banner__close-button').click();
await page.waitForSelector('.c-message-banner__message', { state: 'detached' }); await page.waitForSelector('.c-message-banner__message', { state: 'detached' });
} }
/** /**
@ -223,63 +232,63 @@ async function saveOverlayPlot(page) {
// FIXME: Remove this eslint exception once implemented // FIXME: Remove this eslint exception once implemented
// eslint-disable-next-line no-unused-vars // eslint-disable-next-line no-unused-vars
async function testLogPlotPixels(page) { async function testLogPlotPixels(page) {
const pixelsMatch = await page.evaluate(async () => { const pixelsMatch = await page.evaluate(async () => {
// TODO get canvas pixels at a few locations to make sure they're the correct color, to test that the plot comes out as expected. // TODO get canvas pixels at a few locations to make sure they're the correct color, to test that the plot comes out as expected.
await new Promise((r) => setTimeout(r, 5 * 1000)); await new Promise((r) => setTimeout(r, 5 * 1000));
// These are some pixels that should be blue points in the log plot. // These are some pixels that should be blue points in the log plot.
// If the plot changes shape to an unexpected shape, this will // If the plot changes shape to an unexpected shape, this will
// likely fail, which is what we want. // likely fail, which is what we want.
// //
// I found these pixels by pausing playwright in debug mode at this // I found these pixels by pausing playwright in debug mode at this
// point, and using similar code as below to output the pixel data, then // point, and using similar code as below to output the pixel data, then
// I logged those pixels here. // I logged those pixels here.
const expectedBluePixels = [ const expectedBluePixels = [
// TODO these pixel sets only work with the first test, but not the second test. // TODO these pixel sets only work with the first test, but not the second test.
// [60, 35], // [60, 35],
// [121, 125], // [121, 125],
// [156, 377], // [156, 377],
// [264, 73], // [264, 73],
// [372, 186], // [372, 186],
// [576, 73], // [576, 73],
// [659, 439], // [659, 439],
// [675, 423] // [675, 423]
[60, 35], [60, 35],
[120, 125], [120, 125],
[156, 375], [156, 375],
[264, 73], [264, 73],
[372, 185], [372, 185],
[575, 72], [575, 72],
[659, 437], [659, 437],
[675, 421] [675, 421]
]; ];
// The first canvas in the DOM is the one that has the plot point // The first canvas in the DOM is the one that has the plot point
// icons (canvas 2d), which is the one we are testing. The second // icons (canvas 2d), which is the one we are testing. The second
// one in the DOM is the WebGL canvas with the line. (Why aren't // one in the DOM is the WebGL canvas with the line. (Why aren't
// they both WebGL?) // they both WebGL?)
const canvas = document.querySelector('canvas'); const canvas = document.querySelector('canvas');
const ctx = canvas.getContext('2d'); const ctx = canvas.getContext('2d');
for (const pixel of expectedBluePixels) { for (const pixel of expectedBluePixels) {
// XXX Possible optimization: call getImageData only once with // XXX Possible optimization: call getImageData only once with
// area including all pixels to be tested. // area including all pixels to be tested.
const data = ctx.getImageData(pixel[0], pixel[1], 1, 1).data; const data = ctx.getImageData(pixel[0], pixel[1], 1, 1).data;
// #43b0ffff <-- openmct cyanish-blue with 100% opacity // #43b0ffff <-- openmct cyanish-blue with 100% opacity
// if (data[0] !== 0x43 || data[1] !== 0xb0 || data[2] !== 0xff || data[3] !== 0xff) { // if (data[0] !== 0x43 || data[1] !== 0xb0 || data[2] !== 0xff || data[3] !== 0xff) {
if (data[0] === 0 && data[1] === 0 && data[2] === 0 && data[3] === 0) { if (data[0] === 0 && data[1] === 0 && data[2] === 0 && data[3] === 0) {
// If any pixel is empty, it means we didn't hit a plot point. // If any pixel is empty, it means we didn't hit a plot point.
return false; return false;
} }
} }
return true; return true;
}); });
expect(pixelsMatch).toBe(true); expect(pixelsMatch).toBe(true);
} }

View File

@ -27,55 +27,56 @@ Tests to verify log plot functionality when objects are missing
const { test, expect } = require('../../../../pluginFixtures'); const { test, expect } = require('../../../../pluginFixtures');
test.describe('Handle missing object for plots', () => { test.describe('Handle missing object for plots', () => {
test('Displays empty div for missing stacked plot item @unstable', async ({ page, browserName, openmctConfig }) => { test('Displays empty div for missing stacked plot item @unstable', async ({
// eslint-disable-next-line playwright/no-skipped-test page,
test.skip(browserName === 'firefox', 'Firefox failing due to console events being missed'); browserName,
openmctConfig
}) => {
// eslint-disable-next-line playwright/no-skipped-test
test.skip(browserName === 'firefox', 'Firefox failing due to console events being missed');
const { myItemsFolderName } = openmctConfig; const { myItemsFolderName } = openmctConfig;
const errorLogs = []; const errorLogs = [];
page.on("console", (message) => { page.on('console', (message) => {
if (message.type() === 'warning' && message.text().includes('Missing domain object')) { if (message.type() === 'warning' && message.text().includes('Missing domain object')) {
errorLogs.push(message.text()); errorLogs.push(message.text());
} }
});
//Make stacked plot
await makeStackedPlot(page, myItemsFolderName);
//Gets local storage and deletes the last sine wave generator in the stacked plot
const localStorage = await page.evaluate(() => window.localStorage);
const parsedData = JSON.parse(localStorage.mct);
const keys = Object.keys(parsedData);
const lastKey = keys[keys.length - 1];
delete parsedData[lastKey];
//Sets local storage with missing object
await page.evaluate(
`window.localStorage.setItem('mct', '${JSON.stringify(parsedData)}')`
);
//Reloads page and clicks on stacked plot
await Promise.all([
page.reload(),
page.waitForLoadState('networkidle')
]);
//Verify Main section is there on load
await expect.soft(page.locator('.l-browse-bar__object-name')).toContainText('Unnamed Stacked Plot');
await page.locator(`text=Open MCT ${myItemsFolderName} >> span`).nth(3).click();
await Promise.all([
page.waitForNavigation(),
page.locator('text=Unnamed Stacked Plot').first().click()
]);
//Check that there is only one stacked item plot with a plot, the missing one will be empty
await expect(page.locator(".c-plot--stacked-container:has(.gl-plot)")).toHaveCount(1);
//Verify that console.warn is thrown
expect(errorLogs).toHaveLength(1);
}); });
//Make stacked plot
await makeStackedPlot(page, myItemsFolderName);
//Gets local storage and deletes the last sine wave generator in the stacked plot
const localStorage = await page.evaluate(() => window.localStorage);
const parsedData = JSON.parse(localStorage.mct);
const keys = Object.keys(parsedData);
const lastKey = keys[keys.length - 1];
delete parsedData[lastKey];
//Sets local storage with missing object
await page.evaluate(`window.localStorage.setItem('mct', '${JSON.stringify(parsedData)}')`);
//Reloads page and clicks on stacked plot
await Promise.all([page.reload(), page.waitForLoadState('networkidle')]);
//Verify Main section is there on load
await expect
.soft(page.locator('.l-browse-bar__object-name'))
.toContainText('Unnamed Stacked Plot');
await page.locator(`text=Open MCT ${myItemsFolderName} >> span`).nth(3).click();
await Promise.all([
page.waitForNavigation(),
page.locator('text=Unnamed Stacked Plot').first().click()
]);
//Check that there is only one stacked item plot with a plot, the missing one will be empty
await expect(page.locator('.c-plot--stacked-container:has(.gl-plot)')).toHaveCount(1);
//Verify that console.warn is thrown
expect(errorLogs).toHaveLength(1);
});
}); });
/** /**
@ -83,42 +84,42 @@ test.describe('Handle missing object for plots', () => {
* @private * @private
*/ */
async function makeStackedPlot(page, myItemsFolderName) { async function makeStackedPlot(page, myItemsFolderName) {
// fresh page with time range from 2022-03-29 22:00:00.000Z to 2022-03-29 22:00:30.000Z // fresh page with time range from 2022-03-29 22:00:00.000Z to 2022-03-29 22:00:30.000Z
await page.goto('./', { waitUntil: 'domcontentloaded' }); await page.goto('./', { waitUntil: 'domcontentloaded' });
// create stacked plot // create stacked plot
await page.locator('button.c-create-button').click(); await page.locator('button.c-create-button').click();
await page.locator('li[role="menuitem"]:has-text("Stacked Plot")').click(); await page.locator('li[role="menuitem"]:has-text("Stacked Plot")').click();
await Promise.all([ await Promise.all([
page.waitForNavigation({ waitUntil: 'networkidle'}), page.waitForNavigation({ waitUntil: 'networkidle' }),
page.locator('button:has-text("OK")').click(), page.locator('button:has-text("OK")').click(),
//Wait for Save Banner to appear //Wait for Save Banner to appear
page.waitForSelector('.c-message-banner__message') page.waitForSelector('.c-message-banner__message')
]); ]);
// save the stacked plot // save the stacked plot
await saveStackedPlot(page); await saveStackedPlot(page);
// create a sinewave generator // create a sinewave generator
await createSineWaveGenerator(page); await createSineWaveGenerator(page);
// click on stacked plot // click on stacked plot
await page.locator(`text=Open MCT ${myItemsFolderName} >> span`).nth(3).click(); await page.locator(`text=Open MCT ${myItemsFolderName} >> span`).nth(3).click();
await Promise.all([ await Promise.all([
page.waitForNavigation(), page.waitForNavigation(),
page.locator('text=Unnamed Stacked Plot').first().click() page.locator('text=Unnamed Stacked Plot').first().click()
]); ]);
// create a second sinewave generator // create a second sinewave generator
await createSineWaveGenerator(page); await createSineWaveGenerator(page);
// click on stacked plot // click on stacked plot
await page.locator(`text=Open MCT ${myItemsFolderName} >> span`).nth(3).click(); await page.locator(`text=Open MCT ${myItemsFolderName} >> span`).nth(3).click();
await Promise.all([ await Promise.all([
page.waitForNavigation(), page.waitForNavigation(),
page.locator('text=Unnamed Stacked Plot').first().click() page.locator('text=Unnamed Stacked Plot').first().click()
]); ]);
} }
/** /**
@ -126,17 +127,20 @@ async function makeStackedPlot(page, myItemsFolderName) {
* @private * @private
*/ */
async function saveStackedPlot(page) { async function saveStackedPlot(page) {
// save stacked plot // save stacked plot
await page.locator('text=Snapshot Save and Finish Editing Save and Continue Editing >> button').nth(1).click(); await page
.locator('text=Snapshot Save and Finish Editing Save and Continue Editing >> button')
.nth(1)
.click();
await Promise.all([ await Promise.all([
page.locator('text=Save and Finish Editing').click(), page.locator('text=Save and Finish Editing').click(),
//Wait for Save Banner to appear //Wait for Save Banner to appear
page.waitForSelector('.c-message-banner__message') page.waitForSelector('.c-message-banner__message')
]); ]);
//Wait until Save Banner is gone //Wait until Save Banner is gone
await page.locator('.c-message-banner__close-button').click(); await page.locator('.c-message-banner__close-button').click();
await page.waitForSelector('.c-message-banner__message', { state: 'detached' }); await page.waitForSelector('.c-message-banner__message', { state: 'detached' });
} }
/** /**
@ -144,14 +148,14 @@ async function saveStackedPlot(page) {
* @private * @private
*/ */
async function createSineWaveGenerator(page) { async function createSineWaveGenerator(page) {
//Create sine wave generator //Create sine wave generator
await page.locator('button.c-create-button').click(); await page.locator('button.c-create-button').click();
await page.locator('li[role="menuitem"]:has-text("Sine Wave Generator")').click(); await page.locator('li[role="menuitem"]:has-text("Sine Wave Generator")').click();
await Promise.all([ await Promise.all([
page.waitForNavigation({ waitUntil: 'networkidle'}), page.waitForNavigation({ waitUntil: 'networkidle' }),
page.locator('button:has-text("OK")').click(), page.locator('button:has-text("OK")').click(),
//Wait for Save Banner to appear //Wait for Save Banner to appear
page.waitForSelector('.c-message-banner__message') page.waitForSelector('.c-message-banner__message')
]); ]);
} }

View File

@ -26,201 +26,230 @@ necessarily be used for reference when writing new tests in this area.
*/ */
const { test, expect } = require('../../../../pluginFixtures'); const { test, expect } = require('../../../../pluginFixtures');
const { createDomainObjectWithDefaults, getCanvasPixels, selectInspectorTab, waitForPlotsToRender } = require('../../../../appActions'); const {
createDomainObjectWithDefaults,
getCanvasPixels,
selectInspectorTab,
waitForPlotsToRender
} = require('../../../../appActions');
test.describe('Overlay Plot', () => { test.describe('Overlay Plot', () => {
test.beforeEach(async ({ page }) => { test.beforeEach(async ({ page }) => {
await page.goto('./', { waitUntil: 'domcontentloaded' }); await page.goto('./', { waitUntil: 'domcontentloaded' });
});
test('Plot legend color is in sync with plot series color', async ({ page }) => {
const overlayPlot = await createDomainObjectWithDefaults(page, {
type: 'Overlay Plot'
}); });
test('Plot legend color is in sync with plot series color', async ({ page }) => { await createDomainObjectWithDefaults(page, {
const overlayPlot = await createDomainObjectWithDefaults(page, { type: 'Sine Wave Generator',
type: "Overlay Plot" parent: overlayPlot.uuid
});
await createDomainObjectWithDefaults(page, {
type: "Sine Wave Generator",
parent: overlayPlot.uuid
});
await page.goto(overlayPlot.url);
await selectInspectorTab(page, 'Config');
// navigate to plot series color palette
await page.click('.l-browse-bar__actions__edit');
await page.locator('li.c-tree__item.menus-to-left .c-disclosure-triangle').click();
await page.locator('.c-click-swatch--menu').click();
await page.locator('.c-palette__item[style="background: rgb(255, 166, 61);"]').click();
// gets color for swatch located in legend
const seriesColorSwatch = page.locator('.gl-plot-y-label-swatch-container > .plot-series-color-swatch');
await expect(seriesColorSwatch).toHaveCSS('background-color', 'rgb(255, 166, 61)');
}); });
test('Limit lines persist when series is moved to another Y Axis and on refresh', async ({ page }) => { await page.goto(overlayPlot.url);
test.info().annotations.push({
type: 'issue',
description: 'https://github.com/nasa/openmct/issues/6338'
});
// Create an Overlay Plot with a default SWG
const overlayPlot = await createDomainObjectWithDefaults(page, {
type: "Overlay Plot"
});
const swgA = await createDomainObjectWithDefaults(page, { await selectInspectorTab(page, 'Config');
type: "Sine Wave Generator",
parent: overlayPlot.uuid
});
await page.goto(overlayPlot.url); // navigate to plot series color palette
await page.click('.l-browse-bar__actions__edit');
// Assert that no limit lines are shown by default await page.locator('li.c-tree__item.menus-to-left .c-disclosure-triangle').click();
await page.waitForSelector('.js-limit-area', { state: 'attached' }); await page.locator('.c-click-swatch--menu').click();
expect(await page.locator('.c-plot-limit-line').count()).toBe(0); await page.locator('.c-palette__item[style="background: rgb(255, 166, 61);"]').click();
// gets color for swatch located in legend
// Enter edit mode const seriesColorSwatch = page.locator(
await page.click('button[title="Edit"]'); '.gl-plot-y-label-swatch-container > .plot-series-color-swatch'
);
// Expand the "Sine Wave Generator" plot series options and enable limit lines await expect(seriesColorSwatch).toHaveCSS('background-color', 'rgb(255, 166, 61)');
await selectInspectorTab(page, 'Config'); });
await page.getByRole('list', { name: 'Plot Series Properties' }).locator('span').first().click();
await page.getByRole('list', { name: 'Plot Series Properties' }).locator('[title="Display limit lines"]~div input').check();
await assertLimitLinesExistAndAreVisible(page);
// Save (exit edit mode)
await page.locator('button[title="Save"]').click();
await page.locator('li[title="Save and Finish Editing"]').click();
await assertLimitLinesExistAndAreVisible(page);
await page.reload();
await assertLimitLinesExistAndAreVisible(page);
// Enter edit mode
await page.click('button[title="Edit"]');
await selectInspectorTab(page, 'Elements');
// Drag Sine Wave Generator series from Y Axis 1 into Y Axis 2
await page.locator(`#inspector-elements-tree >> text=${swgA.name}`).dragTo(page.locator('[aria-label="Element Item Group Y Axis 2"]'));
await assertLimitLinesExistAndAreVisible(page);
// Save (exit edit mode)
await page.locator('button[title="Save"]').click();
await page.locator('li[title="Save and Finish Editing"]').click();
await assertLimitLinesExistAndAreVisible(page);
await page.reload();
await assertLimitLinesExistAndAreVisible(page);
test('Limit lines persist when series is moved to another Y Axis and on refresh', async ({
page
}) => {
test.info().annotations.push({
type: 'issue',
description: 'https://github.com/nasa/openmct/issues/6338'
});
// Create an Overlay Plot with a default SWG
const overlayPlot = await createDomainObjectWithDefaults(page, {
type: 'Overlay Plot'
}); });
test('The elements pool supports dragging series into multiple y-axis buckets', async ({ page }) => { const swgA = await createDomainObjectWithDefaults(page, {
const overlayPlot = await createDomainObjectWithDefaults(page, { type: 'Sine Wave Generator',
type: "Overlay Plot" parent: overlayPlot.uuid
});
const swgA = await createDomainObjectWithDefaults(page, {
type: "Sine Wave Generator",
parent: overlayPlot.uuid
});
const swgB = await createDomainObjectWithDefaults(page, {
type: "Sine Wave Generator",
parent: overlayPlot.uuid
});
const swgC = await createDomainObjectWithDefaults(page, {
type: "Sine Wave Generator",
parent: overlayPlot.uuid
});
const swgD = await createDomainObjectWithDefaults(page, {
type: "Sine Wave Generator",
parent: overlayPlot.uuid
});
const swgE = await createDomainObjectWithDefaults(page, {
type: "Sine Wave Generator",
parent: overlayPlot.uuid
});
await page.goto(overlayPlot.url);
await page.click('button[title="Edit"]');
await selectInspectorTab(page, 'Elements');
// Drag swg a, c, e into Y Axis 2
await page.locator(`#inspector-elements-tree >> text=${swgA.name}`).dragTo(page.locator('[aria-label="Element Item Group Y Axis 2"]'));
await page.locator(`#inspector-elements-tree >> text=${swgC.name}`).dragTo(page.locator('[aria-label="Element Item Group Y Axis 2"]'));
await page.locator(`#inspector-elements-tree >> text=${swgE.name}`).dragTo(page.locator('[aria-label="Element Item Group Y Axis 2"]'));
// Assert that Y Axis 1 and Y Axis 2 property groups are visible only
await selectInspectorTab(page, 'Config');
const yAxis1PropertyGroup = page.locator('[aria-label="Y Axis Properties"]');
const yAxis2PropertyGroup = page.locator('[aria-label="Y Axis 2 Properties"]');
const yAxis3PropertyGroup = page.locator('[aria-label="Y Axis 3 Properties"]');
await expect(yAxis1PropertyGroup).toBeVisible();
await expect(yAxis2PropertyGroup).toBeVisible();
await expect(yAxis3PropertyGroup).toBeHidden();
const yAxis1Group = page.getByLabel("Y Axis 1");
const yAxis2Group = page.getByLabel("Y Axis 2");
const yAxis3Group = page.getByLabel("Y Axis 3");
await selectInspectorTab(page, 'Elements');
// Drag swg b into Y Axis 3
await page.locator(`#inspector-elements-tree >> text=${swgB.name}`).dragTo(page.locator('[aria-label="Element Item Group Y Axis 3"]'));
// Assert that all Y Axis property groups are visible
await selectInspectorTab(page, 'Config');
await expect(yAxis1PropertyGroup).toBeVisible();
await expect(yAxis2PropertyGroup).toBeVisible();
await expect(yAxis3PropertyGroup).toBeVisible();
// Verify that the elements are in the correct buckets and in the correct order
await selectInspectorTab(page, 'Elements');
expect(yAxis1Group.getByRole('listitem', { name: swgD.name })).toBeTruthy();
expect(yAxis1Group.getByRole('listitem').nth(0).getByText(swgD.name)).toBeTruthy();
expect(yAxis2Group.getByRole('listitem', { name: swgE.name })).toBeTruthy();
expect(yAxis2Group.getByRole('listitem').nth(0).getByText(swgE.name)).toBeTruthy();
expect(yAxis2Group.getByRole('listitem', { name: swgC.name })).toBeTruthy();
expect(yAxis2Group.getByRole('listitem').nth(1).getByText(swgC.name)).toBeTruthy();
expect(yAxis2Group.getByRole('listitem', { name: swgA.name })).toBeTruthy();
expect(yAxis2Group.getByRole('listitem').nth(2).getByText(swgA.name)).toBeTruthy();
expect(yAxis3Group.getByRole('listitem', { name: swgB.name })).toBeTruthy();
expect(yAxis3Group.getByRole('listitem').nth(0).getByText(swgB.name)).toBeTruthy();
}); });
test('Clicking on an item in the elements pool brings up the plot preview with data points', async ({ page }) => { await page.goto(overlayPlot.url);
const overlayPlot = await createDomainObjectWithDefaults(page, {
type: "Overlay Plot"
});
const swgA = await createDomainObjectWithDefaults(page, { // Assert that no limit lines are shown by default
type: "Sine Wave Generator", await page.waitForSelector('.js-limit-area', { state: 'attached' });
parent: overlayPlot.uuid expect(await page.locator('.c-plot-limit-line').count()).toBe(0);
});
await page.goto(overlayPlot.url); // Enter edit mode
// Wait for plot series data to load and be drawn await page.click('button[title="Edit"]');
await waitForPlotsToRender(page);
await page.click('button[title="Edit"]');
await selectInspectorTab(page, 'Elements'); // Expand the "Sine Wave Generator" plot series options and enable limit lines
await selectInspectorTab(page, 'Config');
await page
.getByRole('list', { name: 'Plot Series Properties' })
.locator('span')
.first()
.click();
await page
.getByRole('list', { name: 'Plot Series Properties' })
.locator('[title="Display limit lines"]~div input')
.check();
await page.locator(`#inspector-elements-tree >> text=${swgA.name}`).click(); await assertLimitLinesExistAndAreVisible(page);
const plotPixels = await getCanvasPixels(page, '.js-overlay canvas'); // Save (exit edit mode)
const plotPixelSize = plotPixels.length; await page.locator('button[title="Save"]').click();
expect(plotPixelSize).toBeGreaterThan(0); await page.locator('li[title="Save and Finish Editing"]').click();
await assertLimitLinesExistAndAreVisible(page);
await page.reload();
await assertLimitLinesExistAndAreVisible(page);
// Enter edit mode
await page.click('button[title="Edit"]');
await selectInspectorTab(page, 'Elements');
// Drag Sine Wave Generator series from Y Axis 1 into Y Axis 2
await page
.locator(`#inspector-elements-tree >> text=${swgA.name}`)
.dragTo(page.locator('[aria-label="Element Item Group Y Axis 2"]'));
await assertLimitLinesExistAndAreVisible(page);
// Save (exit edit mode)
await page.locator('button[title="Save"]').click();
await page.locator('li[title="Save and Finish Editing"]').click();
await assertLimitLinesExistAndAreVisible(page);
await page.reload();
await assertLimitLinesExistAndAreVisible(page);
});
test('The elements pool supports dragging series into multiple y-axis buckets', async ({
page
}) => {
const overlayPlot = await createDomainObjectWithDefaults(page, {
type: 'Overlay Plot'
}); });
const swgA = await createDomainObjectWithDefaults(page, {
type: 'Sine Wave Generator',
parent: overlayPlot.uuid
});
const swgB = await createDomainObjectWithDefaults(page, {
type: 'Sine Wave Generator',
parent: overlayPlot.uuid
});
const swgC = await createDomainObjectWithDefaults(page, {
type: 'Sine Wave Generator',
parent: overlayPlot.uuid
});
const swgD = await createDomainObjectWithDefaults(page, {
type: 'Sine Wave Generator',
parent: overlayPlot.uuid
});
const swgE = await createDomainObjectWithDefaults(page, {
type: 'Sine Wave Generator',
parent: overlayPlot.uuid
});
await page.goto(overlayPlot.url);
await page.click('button[title="Edit"]');
await selectInspectorTab(page, 'Elements');
// Drag swg a, c, e into Y Axis 2
await page
.locator(`#inspector-elements-tree >> text=${swgA.name}`)
.dragTo(page.locator('[aria-label="Element Item Group Y Axis 2"]'));
await page
.locator(`#inspector-elements-tree >> text=${swgC.name}`)
.dragTo(page.locator('[aria-label="Element Item Group Y Axis 2"]'));
await page
.locator(`#inspector-elements-tree >> text=${swgE.name}`)
.dragTo(page.locator('[aria-label="Element Item Group Y Axis 2"]'));
// Assert that Y Axis 1 and Y Axis 2 property groups are visible only
await selectInspectorTab(page, 'Config');
const yAxis1PropertyGroup = page.locator('[aria-label="Y Axis Properties"]');
const yAxis2PropertyGroup = page.locator('[aria-label="Y Axis 2 Properties"]');
const yAxis3PropertyGroup = page.locator('[aria-label="Y Axis 3 Properties"]');
await expect(yAxis1PropertyGroup).toBeVisible();
await expect(yAxis2PropertyGroup).toBeVisible();
await expect(yAxis3PropertyGroup).toBeHidden();
const yAxis1Group = page.getByLabel('Y Axis 1');
const yAxis2Group = page.getByLabel('Y Axis 2');
const yAxis3Group = page.getByLabel('Y Axis 3');
await selectInspectorTab(page, 'Elements');
// Drag swg b into Y Axis 3
await page
.locator(`#inspector-elements-tree >> text=${swgB.name}`)
.dragTo(page.locator('[aria-label="Element Item Group Y Axis 3"]'));
// Assert that all Y Axis property groups are visible
await selectInspectorTab(page, 'Config');
await expect(yAxis1PropertyGroup).toBeVisible();
await expect(yAxis2PropertyGroup).toBeVisible();
await expect(yAxis3PropertyGroup).toBeVisible();
// Verify that the elements are in the correct buckets and in the correct order
await selectInspectorTab(page, 'Elements');
expect(yAxis1Group.getByRole('listitem', { name: swgD.name })).toBeTruthy();
expect(yAxis1Group.getByRole('listitem').nth(0).getByText(swgD.name)).toBeTruthy();
expect(yAxis2Group.getByRole('listitem', { name: swgE.name })).toBeTruthy();
expect(yAxis2Group.getByRole('listitem').nth(0).getByText(swgE.name)).toBeTruthy();
expect(yAxis2Group.getByRole('listitem', { name: swgC.name })).toBeTruthy();
expect(yAxis2Group.getByRole('listitem').nth(1).getByText(swgC.name)).toBeTruthy();
expect(yAxis2Group.getByRole('listitem', { name: swgA.name })).toBeTruthy();
expect(yAxis2Group.getByRole('listitem').nth(2).getByText(swgA.name)).toBeTruthy();
expect(yAxis3Group.getByRole('listitem', { name: swgB.name })).toBeTruthy();
expect(yAxis3Group.getByRole('listitem').nth(0).getByText(swgB.name)).toBeTruthy();
});
test('Clicking on an item in the elements pool brings up the plot preview with data points', async ({
page
}) => {
const overlayPlot = await createDomainObjectWithDefaults(page, {
type: 'Overlay Plot'
});
const swgA = await createDomainObjectWithDefaults(page, {
type: 'Sine Wave Generator',
parent: overlayPlot.uuid
});
await page.goto(overlayPlot.url);
// Wait for plot series data to load and be drawn
await waitForPlotsToRender(page);
await page.click('button[title="Edit"]');
await selectInspectorTab(page, 'Elements');
await page.locator(`#inspector-elements-tree >> text=${swgA.name}`).click();
const plotPixels = await getCanvasPixels(page, '.js-overlay canvas');
const plotPixelSize = plotPixels.length;
expect(plotPixelSize).toBeGreaterThan(0);
});
}); });
/** /**
@ -228,14 +257,14 @@ test.describe('Overlay Plot', () => {
* @param {import('@playwright/test').Page} page * @param {import('@playwright/test').Page} page
*/ */
async function assertLimitLinesExistAndAreVisible(page) { async function assertLimitLinesExistAndAreVisible(page) {
// Wait for plot series data to load // Wait for plot series data to load
await waitForPlotsToRender(page); await waitForPlotsToRender(page);
// Wait for limit lines to be created // Wait for limit lines to be created
await page.waitForSelector('.js-limit-area', { state: 'attached' }); await page.waitForSelector('.js-limit-area', { state: 'attached' });
const limitLineCount = await page.locator('.c-plot-limit-line').count(); const limitLineCount = await page.locator('.c-plot-limit-line').count();
// There should be 10 limit lines created by default // There should be 10 limit lines created by default
expect(await page.locator('.c-plot-limit-line').count()).toBe(10); expect(await page.locator('.c-plot-limit-line').count()).toBe(10);
for (let i = 0; i < limitLineCount; i++) { for (let i = 0; i < limitLineCount; i++) {
await expect(page.locator('.c-plot-limit-line').nth(i)).toBeVisible(); await expect(page.locator('.c-plot-limit-line').nth(i)).toBeVisible();
} }
} }

View File

@ -21,44 +21,46 @@
*****************************************************************************/ *****************************************************************************/
/* /*
* This test suite is dedicated to testing the rendering and interaction of plots. * This test suite is dedicated to testing the rendering and interaction of plots.
* *
*/ */
const { test, expect } = require('../../../../pluginFixtures'); const { test, expect } = require('../../../../pluginFixtures');
const { createDomainObjectWithDefaults, getCanvasPixels } = require('../../../../appActions'); const { createDomainObjectWithDefaults, getCanvasPixels } = require('../../../../appActions');
test.describe('Plot Rendering', () => { test.describe('Plot Rendering', () => {
let sineWaveGeneratorObject; let sineWaveGeneratorObject;
test.beforeEach(async ({ page }) => { test.beforeEach(async ({ page }) => {
// Open a browser, navigate to the main page, and wait until all networkevents to resolve // Open a browser, navigate to the main page, and wait until all networkevents to resolve
await page.goto('./', { waitUntil: 'domcontentloaded' }); await page.goto('./', { waitUntil: 'domcontentloaded' });
sineWaveGeneratorObject = await createDomainObjectWithDefaults(page, { type: 'Sine Wave Generator' }); sineWaveGeneratorObject = await createDomainObjectWithDefaults(page, {
type: 'Sine Wave Generator'
}); });
});
test('Plots do not re-request data when a plot is clicked', async ({ page }) => { test('Plots do not re-request data when a plot is clicked', async ({ page }) => {
// Navigate to Sine Wave Generator // Navigate to Sine Wave Generator
await page.goto(sineWaveGeneratorObject.url); await page.goto(sineWaveGeneratorObject.url);
// Click on the plot canvas // Click on the plot canvas
await page.locator('canvas').nth(1).click(); await page.locator('canvas').nth(1).click();
// No request was made to get historical data // No request was made to get historical data
const createMineFolderRequests = []; const createMineFolderRequests = [];
page.on('request', req => { page.on('request', (req) => {
createMineFolderRequests.push(req); createMineFolderRequests.push(req);
});
expect(createMineFolderRequests.length).toEqual(0);
}); });
expect(createMineFolderRequests.length).toEqual(0);
});
test('Plot is rendered when infinity values exist', async ({ page }) => { test('Plot is rendered when infinity values exist', async ({ page }) => {
// Edit Plot // Edit Plot
await editSineWaveToUseInfinityOption(page, sineWaveGeneratorObject); await editSineWaveToUseInfinityOption(page, sineWaveGeneratorObject);
//Get pixel data from Canvas //Get pixel data from Canvas
const plotPixels = await getCanvasPixels(page, 'canvas'); const plotPixels = await getCanvasPixels(page, 'canvas');
const plotPixelSize = plotPixels.length; const plotPixelSize = plotPixels.length;
expect(plotPixelSize).toBeGreaterThan(0); expect(plotPixelSize).toBeGreaterThan(0);
}); });
}); });
/** /**
@ -69,20 +71,24 @@ test.describe('Plot Rendering', () => {
* @returns {Promise<CreatedObjectInfo>} An object containing information about the edited domain object. * @returns {Promise<CreatedObjectInfo>} An object containing information about the edited domain object.
*/ */
async function editSineWaveToUseInfinityOption(page, sineWaveGeneratorObject) { async function editSineWaveToUseInfinityOption(page, sineWaveGeneratorObject) {
await page.goto(sineWaveGeneratorObject.url); await page.goto(sineWaveGeneratorObject.url);
// Edit SWG properties to include infinity values // Edit SWG properties to include infinity values
await page.locator('[title="More options"]').click(); await page.locator('[title="More options"]').click();
await page.locator('[title="Edit properties of this object."]').click(); await page.locator('[title="Edit properties of this object."]').click();
await page.getByRole('switch', { await page
name: "Include Infinity Values" .getByRole('switch', {
}).check(); name: 'Include Infinity Values'
})
.check();
await page.getByRole('button', { await page
name: 'Save' .getByRole('button', {
}).click(); name: 'Save'
})
.click();
// FIXME: Changes to SWG properties should be reflected on save, but they're not? // FIXME: Changes to SWG properties should be reflected on save, but they're not?
// Thus, navigate away and back to the object. // Thus, navigate away and back to the object.
await page.goto('./#/browse/mine'); await page.goto('./#/browse/mine');
await page.goto(sineWaveGeneratorObject.url); await page.goto(sineWaveGeneratorObject.url);
} }

View File

@ -21,77 +21,89 @@
*****************************************************************************/ *****************************************************************************/
/* /*
* This test suite is dedicated to testing the Scatter Plot component. * This test suite is dedicated to testing the Scatter Plot component.
*/ */
const { test, expect } = require('../../../../pluginFixtures'); const { test, expect } = require('../../../../pluginFixtures');
const { createDomainObjectWithDefaults, selectInspectorTab } = require('../../../../appActions'); const { createDomainObjectWithDefaults, selectInspectorTab } = require('../../../../appActions');
const uuid = require('uuid').v4; const uuid = require('uuid').v4;
test.describe('Scatter Plot', () => { test.describe('Scatter Plot', () => {
let scatterPlot; let scatterPlot;
test.beforeEach(async ({ page }) => { test.beforeEach(async ({ page }) => {
// Open a browser, navigate to the main page, and wait until all networkevents to resolve // Open a browser, navigate to the main page, and wait until all networkevents to resolve
await page.goto('./', { waitUntil: 'domcontentloaded' }); await page.goto('./', { waitUntil: 'domcontentloaded' });
// Create the Scatter Plot // Create the Scatter Plot
scatterPlot = await createDomainObjectWithDefaults(page, { type: 'Scatter Plot' }); scatterPlot = await createDomainObjectWithDefaults(page, { type: 'Scatter Plot' });
});
test('Can add and remove telemetry sources', async ({ page }) => {
const editButton = page.locator('button[title="Edit"]');
const saveButton = page.locator('button[title="Save"]');
// Create a sine wave generator within the scatter plot
const swg1 = await createDomainObjectWithDefaults(page, {
type: 'Sine Wave Generator',
name: `swg-${uuid()}`,
parent: scatterPlot.uuid
}); });
test('Can add and remove telemetry sources', async ({ page }) => { // Navigate to the scatter plot and verify that
const editButton = page.locator('button[title="Edit"]'); // the SWG appears in the elements pool
const saveButton = page.locator('button[title="Save"]'); await page.goto(scatterPlot.url);
await editButton.click();
await selectInspectorTab(page, 'Elements');
await expect.soft(page.locator(`#inspector-elements-tree >> text=${swg1.name}`)).toBeVisible();
await saveButton.click();
await page.locator('li[title="Save and Finish Editing"]').click();
// Create a sine wave generator within the scatter plot // Create another sine wave generator within the scatter plot
const swg1 = await createDomainObjectWithDefaults(page, { const swg2 = await createDomainObjectWithDefaults(page, {
type: 'Sine Wave Generator', type: 'Sine Wave Generator',
name: `swg-${uuid()}`, name: `swg-${uuid()}`,
parent: scatterPlot.uuid parent: scatterPlot.uuid
});
// Navigate to the scatter plot and verify that
// the SWG appears in the elements pool
await page.goto(scatterPlot.url);
await editButton.click();
await selectInspectorTab(page, 'Elements');
await expect.soft(page.locator(`#inspector-elements-tree >> text=${swg1.name}`)).toBeVisible();
await saveButton.click();
await page.locator('li[title="Save and Finish Editing"]').click();
// Create another sine wave generator within the scatter plot
const swg2 = await createDomainObjectWithDefaults(page, {
type: 'Sine Wave Generator',
name: `swg-${uuid()}`,
parent: scatterPlot.uuid
});
// Verify that the 'Replace telemetry source' modal appears and accept it
await expect.soft(page.locator('text=This action will replace the current telemetry source. Do you want to continue?')).toBeVisible();
await page.click('text=Ok');
// Navigate to the scatter plot and verify that the new SWG
// appears in the elements pool and the old one is gone
await page.goto(scatterPlot.url);
await editButton.click();
// Click the "Elements" tab
await selectInspectorTab(page, 'Elements');
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 saveButton.click();
// Right click on the new SWG in the elements pool and delete it
await page.locator(`#inspector-elements-tree >> text=${swg2.name}`).click({
button: 'right'
});
await page.locator('li[title="Remove this object from its containing object."]').click();
// Verify that the 'Remove object' confirmation modal appears and accept it
await expect.soft(page.locator('text=Warning! This action will remove this object. Are you sure you want to continue?')).toBeVisible();
await page.click('text=Ok');
// Verify that the elements pool shows no elements
await expect(page.locator('text="No contained elements"')).toBeVisible();
}); });
// Verify that the 'Replace telemetry source' modal appears and accept it
await expect
.soft(
page.locator(
'text=This action will replace the current telemetry source. Do you want to continue?'
)
)
.toBeVisible();
await page.click('text=Ok');
// Navigate to the scatter plot and verify that the new SWG
// appears in the elements pool and the old one is gone
await page.goto(scatterPlot.url);
await editButton.click();
// Click the "Elements" tab
await selectInspectorTab(page, 'Elements');
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 saveButton.click();
// Right click on the new SWG in the elements pool and delete it
await page.locator(`#inspector-elements-tree >> text=${swg2.name}`).click({
button: 'right'
});
await page.locator('li[title="Remove this object from its containing object."]').click();
// Verify that the 'Remove object' confirmation modal appears and accept it
await expect
.soft(
page.locator(
'text=Warning! This action will remove this object. Are you sure you want to continue?'
)
)
.toBeVisible();
await page.click('text=Ok');
// Verify that the elements pool shows no elements
await expect(page.locator('text="No contained elements"')).toBeVisible();
});
}); });

View File

@ -29,161 +29,202 @@ const { test, expect } = require('../../../../pluginFixtures');
const { createDomainObjectWithDefaults, selectInspectorTab } = require('../../../../appActions'); const { createDomainObjectWithDefaults, selectInspectorTab } = require('../../../../appActions');
test.describe('Stacked Plot', () => { test.describe('Stacked Plot', () => {
let stackedPlot; let stackedPlot;
let swgA; let swgA;
let swgB; let swgB;
let swgC; let swgC;
test.beforeEach(async ({ page }) => { test.beforeEach(async ({ page }) => {
// Open a browser, navigate to the main page, and wait until all networkevents to resolve // Open a browser, navigate to the main page, and wait until all networkevents to resolve
await page.goto('./', { waitUntil: 'domcontentloaded' }); await page.goto('./', { waitUntil: 'domcontentloaded' });
stackedPlot = await createDomainObjectWithDefaults(page, { stackedPlot = await createDomainObjectWithDefaults(page, {
type: "Stacked Plot" type: 'Stacked Plot'
});
swgA = await createDomainObjectWithDefaults(page, {
type: "Sine Wave Generator",
parent: stackedPlot.uuid
});
swgB = await createDomainObjectWithDefaults(page, {
type: "Sine Wave Generator",
parent: stackedPlot.uuid
});
swgC = await createDomainObjectWithDefaults(page, {
type: "Sine Wave Generator",
parent: stackedPlot.uuid
});
}); });
test('Using the remove action removes the correct plot', async ({ page }) => { swgA = await createDomainObjectWithDefaults(page, {
const swgAElementsPoolItem = page.locator('#inspector-elements-tree').locator('.c-object-label', { hasText: swgA.name }); type: 'Sine Wave Generator',
const swgBElementsPoolItem = page.locator('#inspector-elements-tree').locator('.c-object-label', { hasText: swgB.name }); parent: stackedPlot.uuid
const swgCElementsPoolItem = page.locator('#inspector-elements-tree').locator('.c-object-label', { hasText: swgC.name });
await page.goto(stackedPlot.url);
await page.click('button[title="Edit"]');
await selectInspectorTab(page, 'Elements');
await swgBElementsPoolItem.click({ button: 'right' });
await page.getByRole('menuitem').filter({ hasText: /Remove/ }).click();
await page.getByRole('button').filter({ hasText: "OK" }).click();
await expect(page.locator('#inspector-elements-tree .js-elements-pool__item')).toHaveCount(2);
// Confirm that the elements pool contains the items we expect
await expect(swgAElementsPoolItem).toHaveCount(1);
await expect(swgBElementsPoolItem).toHaveCount(0);
await expect(swgCElementsPoolItem).toHaveCount(1);
}); });
swgB = await createDomainObjectWithDefaults(page, {
test('Can reorder Stacked Plot items', async ({ page }) => { type: 'Sine Wave Generator',
const swgAElementsPoolItem = page.locator('#inspector-elements-tree').locator('.c-object-label', { hasText: swgA.name }); parent: stackedPlot.uuid
const swgBElementsPoolItem = page.locator('#inspector-elements-tree').locator('.c-object-label', { hasText: swgB.name });
const swgCElementsPoolItem = page.locator('#inspector-elements-tree').locator('.c-object-label', { hasText: swgC.name });
await page.goto(stackedPlot.url);
await page.click('button[title="Edit"]');
await selectInspectorTab(page, 'Elements');
const stackedPlotItem1 = page.locator('.c-plot--stacked-container').nth(0);
const stackedPlotItem2 = page.locator('.c-plot--stacked-container').nth(1);
const stackedPlotItem3 = page.locator('.c-plot--stacked-container').nth(2);
// assert initial plot order - [swgA, swgB, swgC]
await expect(stackedPlotItem1).toHaveAttribute('aria-label', `Stacked Plot Item ${swgA.name}`);
await expect(stackedPlotItem2).toHaveAttribute('aria-label', `Stacked Plot Item ${swgB.name}`);
await expect(stackedPlotItem3).toHaveAttribute('aria-label', `Stacked Plot Item ${swgC.name}`);
// Drag and drop to reorder - [swgB, swgA, swgC]
await swgBElementsPoolItem.dragTo(swgAElementsPoolItem);
// assert plot order after reorder - [swgB, swgA, swgC]
await expect(stackedPlotItem1).toHaveAttribute('aria-label', `Stacked Plot Item ${swgB.name}`);
await expect(stackedPlotItem2).toHaveAttribute('aria-label', `Stacked Plot Item ${swgA.name}`);
await expect(stackedPlotItem3).toHaveAttribute('aria-label', `Stacked Plot Item ${swgC.name}`);
// Drag and drop to reorder - [swgB, swgC, swgA]
await swgCElementsPoolItem.dragTo(swgAElementsPoolItem);
// assert plot order after second reorder - [swgB, swgC, swgA]
await expect(stackedPlotItem1).toHaveAttribute('aria-label', `Stacked Plot Item ${swgB.name}`);
await expect(stackedPlotItem2).toHaveAttribute('aria-label', `Stacked Plot Item ${swgC.name}`);
await expect(stackedPlotItem3).toHaveAttribute('aria-label', `Stacked Plot Item ${swgA.name}`);
// collapse inspector
await page.locator('.l-shell__pane-inspector .l-pane__collapse-button').click();
// Save (exit edit mode)
await page.locator('button[title="Save"]').click();
await page.locator('li[title="Save and Finish Editing"]').click();
// assert plot order persists after save - [swgB, swgC, swgA]
await expect(stackedPlotItem1).toHaveAttribute('aria-label', `Stacked Plot Item ${swgB.name}`);
await expect(stackedPlotItem2).toHaveAttribute('aria-label', `Stacked Plot Item ${swgC.name}`);
await expect(stackedPlotItem3).toHaveAttribute('aria-label', `Stacked Plot Item ${swgA.name}`);
}); });
swgC = await createDomainObjectWithDefaults(page, {
test('Selecting a child plot while in browse and edit modes shows its properties in the inspector', async ({ page }) => { type: 'Sine Wave Generator',
await page.goto(stackedPlot.url); parent: stackedPlot.uuid
await selectInspectorTab(page, 'Config');
// Click on the 1st plot
await page.locator(`[aria-label="Stacked Plot Item ${swgA.name}"] canvas`).nth(1).click();
// Assert that the inspector shows the Y Axis properties for swgA
await expect(page.locator('[aria-label="Plot Series Properties"] >> h2')).toContainText("Plot Series");
await expect(page.getByRole('heading', { name: "Y Axis" })).toBeVisible();
await expect(page.locator('[aria-label="Plot Series Properties"] .c-object-label')).toContainText(swgA.name);
// Click on the 2nd plot
await page.locator(`[aria-label="Stacked Plot Item ${swgB.name}"] canvas`).nth(1).click();
// Assert that the inspector shows the Y Axis properties for swgB
await expect(page.locator('[aria-label="Plot Series Properties"] >> h2')).toContainText("Plot Series");
await expect(page.getByRole('heading', { name: 'Y Axis' })).toBeVisible();
await expect(page.locator('[aria-label="Plot Series Properties"] .c-object-label')).toContainText(swgB.name);
// Click on the 3rd plot
await page.locator(`[aria-label="Stacked Plot Item ${swgC.name}"] canvas`).nth(1).click();
// Assert that the inspector shows the Y Axis properties for swgC
await expect(page.locator('[aria-label="Plot Series Properties"] >> h2')).toContainText("Plot Series");
await expect(page.getByRole('heading', { name: 'Y Axis' })).toBeVisible();
await expect(page.locator('[aria-label="Plot Series Properties"] .c-object-label')).toContainText(swgC.name);
// Go into edit mode
await page.click('button[title="Edit"]');
await selectInspectorTab(page, 'Config');
// Click on canvas for the 1st plot
await page.locator(`[aria-label="Stacked Plot Item ${swgA.name}"]`).click();
// Assert that the inspector shows the Y Axis properties for swgA
await expect(page.locator('[aria-label="Plot Series Properties"] >> h2')).toContainText("Plot Series");
await expect(page.getByRole('heading', { name: 'Y Axis' })).toBeVisible();
await expect(page.locator('[aria-label="Plot Series Properties"] .c-object-label')).toContainText(swgA.name);
//Click on canvas for the 2nd plot
await page.locator(`[aria-label="Stacked Plot Item ${swgB.name}"]`).click();
// Assert that the inspector shows the Y Axis properties for swgB
await expect(page.locator('[aria-label="Plot Series Properties"] >> h2')).toContainText("Plot Series");
await expect(page.getByRole('heading', { name: 'Y Axis' })).toBeVisible();
await expect(page.locator('[aria-label="Plot Series Properties"] .c-object-label')).toContainText(swgB.name);
//Click on canvas for the 3rd plot
await page.locator(`[aria-label="Stacked Plot Item ${swgC.name}"]`).click();
// Assert that the inspector shows the Y Axis properties for swgC
await expect(page.locator('[aria-label="Plot Series Properties"] >> h2')).toContainText("Plot Series");
await expect(page.getByRole('heading', { name: 'Y Axis' })).toBeVisible();
await expect(page.locator('[aria-label="Plot Series Properties"] .c-object-label')).toContainText(swgC.name);
}); });
});
test('Using the remove action removes the correct plot', async ({ page }) => {
const swgAElementsPoolItem = page
.locator('#inspector-elements-tree')
.locator('.c-object-label', { hasText: swgA.name });
const swgBElementsPoolItem = page
.locator('#inspector-elements-tree')
.locator('.c-object-label', { hasText: swgB.name });
const swgCElementsPoolItem = page
.locator('#inspector-elements-tree')
.locator('.c-object-label', { hasText: swgC.name });
await page.goto(stackedPlot.url);
await page.click('button[title="Edit"]');
await selectInspectorTab(page, 'Elements');
await swgBElementsPoolItem.click({ button: 'right' });
await page
.getByRole('menuitem')
.filter({ hasText: /Remove/ })
.click();
await page.getByRole('button').filter({ hasText: 'OK' }).click();
await expect(page.locator('#inspector-elements-tree .js-elements-pool__item')).toHaveCount(2);
// Confirm that the elements pool contains the items we expect
await expect(swgAElementsPoolItem).toHaveCount(1);
await expect(swgBElementsPoolItem).toHaveCount(0);
await expect(swgCElementsPoolItem).toHaveCount(1);
});
test('Can reorder Stacked Plot items', async ({ page }) => {
const swgAElementsPoolItem = page
.locator('#inspector-elements-tree')
.locator('.c-object-label', { hasText: swgA.name });
const swgBElementsPoolItem = page
.locator('#inspector-elements-tree')
.locator('.c-object-label', { hasText: swgB.name });
const swgCElementsPoolItem = page
.locator('#inspector-elements-tree')
.locator('.c-object-label', { hasText: swgC.name });
await page.goto(stackedPlot.url);
await page.click('button[title="Edit"]');
await selectInspectorTab(page, 'Elements');
const stackedPlotItem1 = page.locator('.c-plot--stacked-container').nth(0);
const stackedPlotItem2 = page.locator('.c-plot--stacked-container').nth(1);
const stackedPlotItem3 = page.locator('.c-plot--stacked-container').nth(2);
// assert initial plot order - [swgA, swgB, swgC]
await expect(stackedPlotItem1).toHaveAttribute('aria-label', `Stacked Plot Item ${swgA.name}`);
await expect(stackedPlotItem2).toHaveAttribute('aria-label', `Stacked Plot Item ${swgB.name}`);
await expect(stackedPlotItem3).toHaveAttribute('aria-label', `Stacked Plot Item ${swgC.name}`);
// Drag and drop to reorder - [swgB, swgA, swgC]
await swgBElementsPoolItem.dragTo(swgAElementsPoolItem);
// assert plot order after reorder - [swgB, swgA, swgC]
await expect(stackedPlotItem1).toHaveAttribute('aria-label', `Stacked Plot Item ${swgB.name}`);
await expect(stackedPlotItem2).toHaveAttribute('aria-label', `Stacked Plot Item ${swgA.name}`);
await expect(stackedPlotItem3).toHaveAttribute('aria-label', `Stacked Plot Item ${swgC.name}`);
// Drag and drop to reorder - [swgB, swgC, swgA]
await swgCElementsPoolItem.dragTo(swgAElementsPoolItem);
// assert plot order after second reorder - [swgB, swgC, swgA]
await expect(stackedPlotItem1).toHaveAttribute('aria-label', `Stacked Plot Item ${swgB.name}`);
await expect(stackedPlotItem2).toHaveAttribute('aria-label', `Stacked Plot Item ${swgC.name}`);
await expect(stackedPlotItem3).toHaveAttribute('aria-label', `Stacked Plot Item ${swgA.name}`);
// collapse inspector
await page.locator('.l-shell__pane-inspector .l-pane__collapse-button').click();
// Save (exit edit mode)
await page.locator('button[title="Save"]').click();
await page.locator('li[title="Save and Finish Editing"]').click();
// assert plot order persists after save - [swgB, swgC, swgA]
await expect(stackedPlotItem1).toHaveAttribute('aria-label', `Stacked Plot Item ${swgB.name}`);
await expect(stackedPlotItem2).toHaveAttribute('aria-label', `Stacked Plot Item ${swgC.name}`);
await expect(stackedPlotItem3).toHaveAttribute('aria-label', `Stacked Plot Item ${swgA.name}`);
});
test('Selecting a child plot while in browse and edit modes shows its properties in the inspector', async ({
page
}) => {
await page.goto(stackedPlot.url);
await selectInspectorTab(page, 'Config');
// Click on the 1st plot
await page.locator(`[aria-label="Stacked Plot Item ${swgA.name}"] canvas`).nth(1).click();
// Assert that the inspector shows the Y Axis properties for swgA
await expect(page.locator('[aria-label="Plot Series Properties"] >> h2')).toContainText(
'Plot Series'
);
await expect(page.getByRole('heading', { name: 'Y Axis' })).toBeVisible();
await expect(
page.locator('[aria-label="Plot Series Properties"] .c-object-label')
).toContainText(swgA.name);
// Click on the 2nd plot
await page.locator(`[aria-label="Stacked Plot Item ${swgB.name}"] canvas`).nth(1).click();
// Assert that the inspector shows the Y Axis properties for swgB
await expect(page.locator('[aria-label="Plot Series Properties"] >> h2')).toContainText(
'Plot Series'
);
await expect(page.getByRole('heading', { name: 'Y Axis' })).toBeVisible();
await expect(
page.locator('[aria-label="Plot Series Properties"] .c-object-label')
).toContainText(swgB.name);
// Click on the 3rd plot
await page.locator(`[aria-label="Stacked Plot Item ${swgC.name}"] canvas`).nth(1).click();
// Assert that the inspector shows the Y Axis properties for swgC
await expect(page.locator('[aria-label="Plot Series Properties"] >> h2')).toContainText(
'Plot Series'
);
await expect(page.getByRole('heading', { name: 'Y Axis' })).toBeVisible();
await expect(
page.locator('[aria-label="Plot Series Properties"] .c-object-label')
).toContainText(swgC.name);
// Go into edit mode
await page.click('button[title="Edit"]');
await selectInspectorTab(page, 'Config');
// Click on canvas for the 1st plot
await page.locator(`[aria-label="Stacked Plot Item ${swgA.name}"]`).click();
// Assert that the inspector shows the Y Axis properties for swgA
await expect(page.locator('[aria-label="Plot Series Properties"] >> h2')).toContainText(
'Plot Series'
);
await expect(page.getByRole('heading', { name: 'Y Axis' })).toBeVisible();
await expect(
page.locator('[aria-label="Plot Series Properties"] .c-object-label')
).toContainText(swgA.name);
//Click on canvas for the 2nd plot
await page.locator(`[aria-label="Stacked Plot Item ${swgB.name}"]`).click();
// Assert that the inspector shows the Y Axis properties for swgB
await expect(page.locator('[aria-label="Plot Series Properties"] >> h2')).toContainText(
'Plot Series'
);
await expect(page.getByRole('heading', { name: 'Y Axis' })).toBeVisible();
await expect(
page.locator('[aria-label="Plot Series Properties"] .c-object-label')
).toContainText(swgB.name);
//Click on canvas for the 3rd plot
await page.locator(`[aria-label="Stacked Plot Item ${swgC.name}"]`).click();
// Assert that the inspector shows the Y Axis properties for swgC
await expect(page.locator('[aria-label="Plot Series Properties"] >> h2')).toContainText(
'Plot Series'
);
await expect(page.getByRole('heading', { name: 'Y Axis' })).toBeVisible();
await expect(
page.locator('[aria-label="Plot Series Properties"] .c-object-label')
).toContainText(swgC.name);
});
}); });

View File

@ -25,237 +25,250 @@ Tests to verify plot tagging functionality.
*/ */
const { test, expect } = require('../../../../pluginFixtures'); const { test, expect } = require('../../../../pluginFixtures');
const { createDomainObjectWithDefaults, setRealTimeMode, setFixedTimeMode, waitForPlotsToRender } = require('../../../../appActions'); const {
createDomainObjectWithDefaults,
setRealTimeMode,
setFixedTimeMode,
waitForPlotsToRender
} = require('../../../../appActions');
test.describe('Plot Tagging', () => { test.describe('Plot Tagging', () => {
/** /**
* 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.
* @param {import('@playwright/test').Page} page * @param {import('@playwright/test').Page} page
* @param {HTMLCanvasElement} canvas a telemetry item with a plot * @param {HTMLCanvasElement} canvas a telemetry item with a plot
* @param {Number} xEnd a telemetry item with a plot * @param {Number} xEnd a telemetry item with a plot
* @param {Number} yEnd a telemetry item with a plot * @param {Number} yEnd a telemetry item with a plot
* @returns {Promise} * @returns {Promise}
*/ */
async function createTags({page, canvas, xEnd, yEnd}) { async function createTags({ page, canvas, xEnd, yEnd }) {
await canvas.hover({trial: true}); await canvas.hover({ trial: true });
//Alt+Shift Drag Start to select some points to tag //Alt+Shift Drag Start to select some points to tag
await page.keyboard.down('Alt'); await page.keyboard.down('Alt');
await page.keyboard.down('Shift'); await page.keyboard.down('Shift');
await canvas.dragTo(canvas, { await canvas.dragTo(canvas, {
sourcePosition: { sourcePosition: {
x: 1, x: 1,
y: 1 y: 1
}, },
targetPosition: { targetPosition: {
x: xEnd, x: xEnd,
y: yEnd y: yEnd
} }
});
//Alt Drag End
await page.keyboard.up('Alt');
await page.keyboard.up('Shift');
//Wait for canvas to stablize.
await canvas.hover({trial: true});
// add some tags
await page.getByText('Annotations').click();
await page.getByRole('button', { name: /Add Tag/ }).click();
await page.getByPlaceholder('Type to select tag').click();
await page.getByText('Driving').click();
await page.getByRole('button', { name: /Add Tag/ }).click();
await page.getByPlaceholder('Type to select tag').click();
await page.getByText('Science').click();
}
/**
* Given a telemetry item (e.g., a Sine Wave Generator) with a plot, tests that the plot can be tagged.
* @param {import('@playwright/test').Page} page
* @param {import('../../../../appActions').CreatedObjectInfo} telemetryItem a telemetry item with a plot
* @returns {Promise}
*/
async function testTelemetryItem(page, telemetryItem) {
// Check that telemetry item also received the tag
await page.goto(telemetryItem.url);
await expect(page.getByText('No tags to display for this item')).toBeVisible();
const canvas = page.locator('canvas').nth(1);
//Wait for canvas to stablize.
await canvas.hover({trial: true});
// click on the tagged plot point
await canvas.click({
position: {
x: 325,
y: 377
}
});
await expect(page.getByText('Science')).toBeVisible();
await expect(page.getByText('Driving')).toBeHidden();
}
/**
* Given a page, tests that tags are searchable, deletable, and persist across reloads.
* @param {import('@playwright/test').Page} page
* @returns {Promise}
*/
async function basicTagsTests(page) {
// Search for Driving
await page.locator('[aria-label="OpenMCT Search"] input[type="search"]').click();
// Clicking elsewhere should cause annotation selection to be cleared
await expect(page.getByText('No tags to display for this item')).toBeVisible();
await page.locator('[aria-label="OpenMCT Search"] input[type="search"]').fill('driv');
// click on the search result
await page.getByRole('searchbox', { name: 'OpenMCT Search' }).getByText(/Sine Wave/).first().click();
// Delete Driving
await page.hover('[aria-label="Tag"]:has-text("Driving")');
await page.locator('[aria-label="Remove tag Driving"]').click();
// Search for Science
await page.locator('[aria-label="OpenMCT Search"] input[type="search"]').click();
await page.locator('[aria-label="OpenMCT Search"] input[type="search"]').fill('sc');
await expect(page.locator('[aria-label="Search Result"]').nth(0)).toContainText("Science");
await expect(page.locator('[aria-label="Search Result"]').nth(0)).not.toContainText("Drilling");
// Search for Driving
await page.locator('[aria-label="OpenMCT Search"] input[type="search"]').click();
await page.locator('[aria-label="OpenMCT Search"] input[type="search"]').fill('driv');
await expect(page.getByText('No results found')).toBeVisible();
//Reload Page
await page.reload({ waitUntil: 'domcontentloaded' });
// wait for plots to load
await waitForPlotsToRender(page);
await page.getByText('Annotations').click();
await expect(page.getByText('No tags to display for this item')).toBeVisible();
const canvas = page.locator('canvas').nth(1);
// click on the tagged plot point
await canvas.click({
position: {
x: 100,
y: 100
}
});
await expect(page.getByText('Science')).toBeVisible();
await expect(page.getByText('Driving')).toBeHidden();
}
test.beforeEach(async ({ page }) => {
await page.goto('./', { waitUntil: 'domcontentloaded' });
}); });
test('Tags work with Overlay Plots', async ({ page }) => { //Alt Drag End
//Test.slow decorator is currently broken. Needs to be fixed in https://github.com/nasa/openmct/issues/5374 await page.keyboard.up('Alt');
test.slow(); await page.keyboard.up('Shift');
const overlayPlot = await createDomainObjectWithDefaults(page, { //Wait for canvas to stablize.
type: "Overlay Plot" await canvas.hover({ trial: true });
});
const alphaSineWave = await createDomainObjectWithDefaults(page, { // add some tags
type: "Sine Wave Generator", await page.getByText('Annotations').click();
name: "Alpha Sine Wave", await page.getByRole('button', { name: /Add Tag/ }).click();
parent: overlayPlot.uuid await page.getByPlaceholder('Type to select tag').click();
}); await page.getByText('Driving').click();
await createDomainObjectWithDefaults(page, { await page.getByRole('button', { name: /Add Tag/ }).click();
type: "Sine Wave Generator", await page.getByPlaceholder('Type to select tag').click();
name: "Beta Sine Wave", await page.getByText('Science').click();
parent: overlayPlot.uuid }
});
await page.goto(overlayPlot.url); /**
* Given a telemetry item (e.g., a Sine Wave Generator) with a plot, tests that the plot can be tagged.
* @param {import('@playwright/test').Page} page
* @param {import('../../../../appActions').CreatedObjectInfo} telemetryItem a telemetry item with a plot
* @returns {Promise}
*/
async function testTelemetryItem(page, telemetryItem) {
// Check that telemetry item also received the tag
await page.goto(telemetryItem.url);
let canvas = page.locator('canvas').nth(1); await expect(page.getByText('No tags to display for this item')).toBeVisible();
// Switch to real-time mode const canvas = page.locator('canvas').nth(1);
// Adding tags should pause the plot
await setRealTimeMode(page);
await createTags({ //Wait for canvas to stablize.
page, await canvas.hover({ trial: true });
canvas,
xEnd: 700,
yEnd: 480
});
await setFixedTimeMode(page); // click on the tagged plot point
await canvas.click({
await basicTagsTests(page); position: {
await testTelemetryItem(page, alphaSineWave); x: 325,
y: 377
// set to real time mode }
await setRealTimeMode(page);
// Search for Science
await page.locator('[aria-label="OpenMCT Search"] input[type="search"]').click();
await page.locator('[aria-label="OpenMCT Search"] input[type="search"]').fill('sc');
// click on the search result
await page.getByRole('searchbox', { name: 'OpenMCT Search' }).getByText('Alpha Sine Wave').first().click();
// wait for plots to load
await expect(page.locator('.js-series-data-loaded')).toBeVisible();
// expect plot to be paused
await expect(page.locator('[title="Resume displaying real-time data"]')).toBeVisible();
await setFixedTimeMode(page);
}); });
test('Tags work with Plot View of telemetry items', async ({ page }) => { await expect(page.getByText('Science')).toBeVisible();
await createDomainObjectWithDefaults(page, { await expect(page.getByText('Driving')).toBeHidden();
type: "Sine Wave Generator" }
});
const canvas = page.locator('canvas').nth(1); /**
await createTags({ * Given a page, tests that tags are searchable, deletable, and persist across reloads.
page, * @param {import('@playwright/test').Page} page
canvas, * @returns {Promise}
xEnd: 700, */
yEnd: 480 async function basicTagsTests(page) {
}); // Search for Driving
await basicTagsTests(page); await page.locator('[aria-label="OpenMCT Search"] input[type="search"]').click();
// Clicking elsewhere should cause annotation selection to be cleared
await expect(page.getByText('No tags to display for this item')).toBeVisible();
await page.locator('[aria-label="OpenMCT Search"] input[type="search"]').fill('driv');
// click on the search result
await page
.getByRole('searchbox', { name: 'OpenMCT Search' })
.getByText(/Sine Wave/)
.first()
.click();
// Delete Driving
await page.hover('[aria-label="Tag"]:has-text("Driving")');
await page.locator('[aria-label="Remove tag Driving"]').click();
// Search for Science
await page.locator('[aria-label="OpenMCT Search"] input[type="search"]').click();
await page.locator('[aria-label="OpenMCT Search"] input[type="search"]').fill('sc');
await expect(page.locator('[aria-label="Search Result"]').nth(0)).toContainText('Science');
await expect(page.locator('[aria-label="Search Result"]').nth(0)).not.toContainText('Drilling');
// Search for Driving
await page.locator('[aria-label="OpenMCT Search"] input[type="search"]').click();
await page.locator('[aria-label="OpenMCT Search"] input[type="search"]').fill('driv');
await expect(page.getByText('No results found')).toBeVisible();
//Reload Page
await page.reload({ waitUntil: 'domcontentloaded' });
// wait for plots to load
await waitForPlotsToRender(page);
await page.getByText('Annotations').click();
await expect(page.getByText('No tags to display for this item')).toBeVisible();
const canvas = page.locator('canvas').nth(1);
// click on the tagged plot point
await canvas.click({
position: {
x: 100,
y: 100
}
}); });
test('Tags work with Stacked Plots', async ({ page }) => { await expect(page.getByText('Science')).toBeVisible();
const stackedPlot = await createDomainObjectWithDefaults(page, { await expect(page.getByText('Driving')).toBeHidden();
type: "Stacked Plot" }
});
const alphaSineWave = await createDomainObjectWithDefaults(page, { test.beforeEach(async ({ page }) => {
type: "Sine Wave Generator", await page.goto('./', { waitUntil: 'domcontentloaded' });
name: "Alpha Sine Wave", });
parent: stackedPlot.uuid
});
await createDomainObjectWithDefaults(page, { test('Tags work with Overlay Plots', async ({ page }) => {
type: "Sine Wave Generator", //Test.slow decorator is currently broken. Needs to be fixed in https://github.com/nasa/openmct/issues/5374
name: "Beta Sine Wave", test.slow();
parent: stackedPlot.uuid
});
await page.goto(stackedPlot.url); const overlayPlot = await createDomainObjectWithDefaults(page, {
type: 'Overlay Plot'
const canvas = page.locator('canvas').nth(1);
await createTags({
page,
canvas,
xEnd: 700,
yEnd: 215
});
await basicTagsTests(page);
await testTelemetryItem(page, alphaSineWave);
}); });
const alphaSineWave = await createDomainObjectWithDefaults(page, {
type: 'Sine Wave Generator',
name: 'Alpha Sine Wave',
parent: overlayPlot.uuid
});
await createDomainObjectWithDefaults(page, {
type: 'Sine Wave Generator',
name: 'Beta Sine Wave',
parent: overlayPlot.uuid
});
await page.goto(overlayPlot.url);
let canvas = page.locator('canvas').nth(1);
// Switch to real-time mode
// Adding tags should pause the plot
await setRealTimeMode(page);
await createTags({
page,
canvas,
xEnd: 700,
yEnd: 480
});
await setFixedTimeMode(page);
await basicTagsTests(page);
await testTelemetryItem(page, alphaSineWave);
// set to real time mode
await setRealTimeMode(page);
// Search for Science
await page.locator('[aria-label="OpenMCT Search"] input[type="search"]').click();
await page.locator('[aria-label="OpenMCT Search"] input[type="search"]').fill('sc');
// click on the search result
await page
.getByRole('searchbox', { name: 'OpenMCT Search' })
.getByText('Alpha Sine Wave')
.first()
.click();
// wait for plots to load
await expect(page.locator('.js-series-data-loaded')).toBeVisible();
// expect plot to be paused
await expect(page.locator('[title="Resume displaying real-time data"]')).toBeVisible();
await setFixedTimeMode(page);
});
test('Tags work with Plot View of telemetry items', async ({ page }) => {
await createDomainObjectWithDefaults(page, {
type: 'Sine Wave Generator'
});
const canvas = page.locator('canvas').nth(1);
await createTags({
page,
canvas,
xEnd: 700,
yEnd: 480
});
await basicTagsTests(page);
});
test('Tags work with Stacked Plots', async ({ page }) => {
const stackedPlot = await createDomainObjectWithDefaults(page, {
type: 'Stacked Plot'
});
const alphaSineWave = await createDomainObjectWithDefaults(page, {
type: 'Sine Wave Generator',
name: 'Alpha Sine Wave',
parent: stackedPlot.uuid
});
await createDomainObjectWithDefaults(page, {
type: 'Sine Wave Generator',
name: 'Beta Sine Wave',
parent: stackedPlot.uuid
});
await page.goto(stackedPlot.url);
const canvas = page.locator('canvas').nth(1);
await createTags({
page,
canvas,
xEnd: 700,
yEnd: 215
});
await basicTagsTests(page);
await testTelemetryItem(page, alphaSineWave);
});
}); });

View File

@ -24,52 +24,59 @@ const { createDomainObjectWithDefaults } = require('../../../../appActions');
const { test, expect } = require('../../../../pluginFixtures'); const { test, expect } = require('../../../../pluginFixtures');
test.describe('Telemetry Table', () => { test.describe('Telemetry Table', () => {
test('unpauses and filters data when paused by button and user changes bounds', async ({ page }) => { test('unpauses and filters data when paused by button and user changes bounds', async ({
test.info().annotations.push({ page
type: 'issue', }) => {
description: 'https://github.com/nasa/openmct/issues/5113' test.info().annotations.push({
}); type: 'issue',
description: 'https://github.com/nasa/openmct/issues/5113'
await page.goto('./', { waitUntil: 'domcontentloaded' });
const table = await createDomainObjectWithDefaults(page, { type: 'Telemetry Table' });
await createDomainObjectWithDefaults(page, {
type: 'Sine Wave Generator',
parent: table.uuid
});
// focus the Telemetry Table
page.goto(table.url);
// Click pause button
const pauseButton = page.locator('button.c-button.icon-pause');
await pauseButton.click();
const tableWrapper = page.locator('div.c-table-wrapper');
await expect(tableWrapper).toHaveClass(/is-paused/);
// Subtract 5 minutes from the current end bound datetime and set it
const endTimeInput = page.locator('input[type="text"].c-input--datetime').nth(1);
await endTimeInput.click();
let endDate = await endTimeInput.inputValue();
endDate = new Date(endDate);
endDate.setUTCMinutes(endDate.getUTCMinutes() - 5);
endDate = endDate.toISOString().replace(/T/, ' ');
await endTimeInput.fill('');
await endTimeInput.fill(endDate);
await page.keyboard.press('Enter');
await expect(tableWrapper).not.toHaveClass(/is-paused/);
// Get the most recent telemetry date
const latestTelemetryDate = await page.locator('table.c-telemetry-table__body > tbody > tr').last().locator('td').nth(1).getAttribute('title');
// Verify that it is <= our new end bound
const latestMilliseconds = Date.parse(latestTelemetryDate);
const endBoundMilliseconds = Date.parse(endDate);
expect(latestMilliseconds).toBeLessThanOrEqual(endBoundMilliseconds);
}); });
await page.goto('./', { waitUntil: 'domcontentloaded' });
const table = await createDomainObjectWithDefaults(page, { type: 'Telemetry Table' });
await createDomainObjectWithDefaults(page, {
type: 'Sine Wave Generator',
parent: table.uuid
});
// focus the Telemetry Table
page.goto(table.url);
// Click pause button
const pauseButton = page.locator('button.c-button.icon-pause');
await pauseButton.click();
const tableWrapper = page.locator('div.c-table-wrapper');
await expect(tableWrapper).toHaveClass(/is-paused/);
// Subtract 5 minutes from the current end bound datetime and set it
const endTimeInput = page.locator('input[type="text"].c-input--datetime').nth(1);
await endTimeInput.click();
let endDate = await endTimeInput.inputValue();
endDate = new Date(endDate);
endDate.setUTCMinutes(endDate.getUTCMinutes() - 5);
endDate = endDate.toISOString().replace(/T/, ' ');
await endTimeInput.fill('');
await endTimeInput.fill(endDate);
await page.keyboard.press('Enter');
await expect(tableWrapper).not.toHaveClass(/is-paused/);
// Get the most recent telemetry date
const latestTelemetryDate = await page
.locator('table.c-telemetry-table__body > tbody > tr')
.last()
.locator('td')
.nth(1)
.getAttribute('title');
// Verify that it is <= our new end bound
const latestMilliseconds = Date.parse(latestTelemetryDate);
const endBoundMilliseconds = Date.parse(endDate);
expect(latestMilliseconds).toBeLessThanOrEqual(endBoundMilliseconds);
});
}); });

View File

@ -21,170 +21,200 @@
*****************************************************************************/ *****************************************************************************/
const { test, expect } = require('../../../../pluginFixtures'); const { test, expect } = require('../../../../pluginFixtures');
const { setFixedTimeMode, setRealTimeMode, setStartOffset, setEndOffset } = require('../../../../appActions'); const {
setFixedTimeMode,
setRealTimeMode,
setStartOffset,
setEndOffset
} = require('../../../../appActions');
test.describe('Time conductor operations', () => { test.describe('Time conductor operations', () => {
test('validate start time does not exceeds end time', async ({ page }) => { test('validate start time does not exceeds end time', async ({ page }) => {
// Go to baseURL // Go to baseURL
await page.goto('./', { waitUntil: 'domcontentloaded' }); await page.goto('./', { waitUntil: 'domcontentloaded' });
const year = new Date().getFullYear(); const year = new Date().getFullYear();
let startDate = 'xxxx-01-01 01:00:00.000Z'; let startDate = 'xxxx-01-01 01:00:00.000Z';
startDate = year + startDate.substring(4); startDate = year + startDate.substring(4);
let endDate = 'xxxx-01-01 02:00:00.000Z'; let endDate = 'xxxx-01-01 02:00:00.000Z';
endDate = year + endDate.substring(4); endDate = year + endDate.substring(4);
const startTimeLocator = page.locator('input[type="text"]').first(); const startTimeLocator = page.locator('input[type="text"]').first();
const endTimeLocator = page.locator('input[type="text"]').nth(1); const endTimeLocator = page.locator('input[type="text"]').nth(1);
// Click start time // Click start time
await startTimeLocator.click(); await startTimeLocator.click();
// Click end time // Click end time
await endTimeLocator.click(); await endTimeLocator.click();
await endTimeLocator.fill(endDate.toString()); await endTimeLocator.fill(endDate.toString());
await startTimeLocator.fill(startDate.toString()); await startTimeLocator.fill(startDate.toString());
// invalid start date // invalid start date
startDate = (year + 1) + startDate.substring(4); startDate = year + 1 + startDate.substring(4);
await startTimeLocator.fill(startDate.toString()); await startTimeLocator.fill(startDate.toString());
await endTimeLocator.click(); await endTimeLocator.click();
const startDateValidityStatus = await startTimeLocator.evaluate((element) => element.checkValidity()); const startDateValidityStatus = await startTimeLocator.evaluate((element) =>
expect(startDateValidityStatus).not.toBeTruthy(); element.checkValidity()
);
expect(startDateValidityStatus).not.toBeTruthy();
// fix to valid start date // fix to valid start date
startDate = (year - 1) + startDate.substring(4); startDate = year - 1 + startDate.substring(4);
await startTimeLocator.fill(startDate.toString()); await startTimeLocator.fill(startDate.toString());
// invalid end date // invalid end date
endDate = (year - 2) + endDate.substring(4); endDate = year - 2 + endDate.substring(4);
await endTimeLocator.fill(endDate.toString()); await endTimeLocator.fill(endDate.toString());
await startTimeLocator.click(); await startTimeLocator.click();
const endDateValidityStatus = await endTimeLocator.evaluate((element) => element.checkValidity()); const endDateValidityStatus = await endTimeLocator.evaluate((element) =>
expect(endDateValidityStatus).not.toBeTruthy(); element.checkValidity()
}); );
expect(endDateValidityStatus).not.toBeTruthy();
});
}); });
// Testing instructions: // Testing instructions:
// Try to change the realtime offsets when in realtime (local clock) mode. // Try to change the realtime offsets when in realtime (local clock) mode.
test.describe('Time conductor input fields real-time mode', () => { test.describe('Time conductor input fields real-time mode', () => {
test('validate input fields in real-time mode', async ({ page }) => { test('validate input fields in real-time mode', async ({ page }) => {
const startOffset = { const startOffset = {
secs: '23' secs: '23'
}; };
const endOffset = { const endOffset = {
secs: '31' secs: '31'
}; };
// Go to baseURL // Go to baseURL
await page.goto('./', { waitUntil: 'domcontentloaded' }); await page.goto('./', { waitUntil: 'domcontentloaded' });
// Switch to real-time mode // Switch to real-time mode
await setRealTimeMode(page); await setRealTimeMode(page);
// Set start time offset // Set start time offset
await setStartOffset(page, startOffset); await setStartOffset(page, startOffset);
// Verify time was updated on time offset button // Verify time was updated on time offset button
await expect(page.locator('data-testid=conductor-start-offset-button')).toContainText('00:30:23'); await expect(page.locator('data-testid=conductor-start-offset-button')).toContainText(
'00:30:23'
);
// Set end time offset // Set end time offset
await setEndOffset(page, endOffset); await setEndOffset(page, endOffset);
// Verify time was updated on preceding time offset button // Verify time was updated on preceding time offset button
await expect(page.locator('data-testid=conductor-end-offset-button')).toContainText('00:00:31'); await expect(page.locator('data-testid=conductor-end-offset-button')).toContainText('00:00:31');
}); });
/** /**
* Verify that offsets and url params are preserved when switching * Verify that offsets and url params are preserved when switching
* between fixed timespan and real-time mode. * between fixed timespan and real-time mode.
*/ */
test('preserve offsets and url params when switching between fixed and real-time mode', async ({ page }) => { test('preserve offsets and url params when switching between fixed and real-time mode', async ({
const startOffset = { page
mins: '30', }) => {
secs: '23' const startOffset = {
}; mins: '30',
secs: '23'
};
const endOffset = { const endOffset = {
secs: '01' secs: '01'
}; };
// Convert offsets to milliseconds // Convert offsets to milliseconds
const startDelta = (30 * 60 * 1000) + (23 * 1000); const startDelta = 30 * 60 * 1000 + 23 * 1000;
const endDelta = (1 * 1000); const endDelta = 1 * 1000;
// Go to baseURL // Go to baseURL
await page.goto('./', { waitUntil: 'domcontentloaded' }); await page.goto('./', { waitUntil: 'domcontentloaded' });
// Switch to real-time mode // Switch to real-time mode
await setRealTimeMode(page); await setRealTimeMode(page);
// Set start time offset // Set start time offset
await setStartOffset(page, startOffset); await setStartOffset(page, startOffset);
// Set end time offset // Set end time offset
await setEndOffset(page, endOffset); await setEndOffset(page, endOffset);
// Switch to fixed timespan mode // Switch to fixed timespan mode
await setFixedTimeMode(page); await setFixedTimeMode(page);
// Switch back to real-time mode // Switch back to real-time mode
await setRealTimeMode(page); await setRealTimeMode(page);
// Verify updated start time offset persists after mode switch // Verify updated start time offset persists after mode switch
await expect(page.locator('data-testid=conductor-start-offset-button')).toContainText('00:30:23'); await expect(page.locator('data-testid=conductor-start-offset-button')).toContainText(
'00:30:23'
);
// Verify updated end time offset persists after mode switch // Verify updated end time offset persists after mode switch
await expect(page.locator('data-testid=conductor-end-offset-button')).toContainText('00:00:01'); await expect(page.locator('data-testid=conductor-end-offset-button')).toContainText('00:00:01');
// Verify url parameters persist after mode switch // Verify url parameters persist after mode switch
await page.waitForNavigation({ waitUntil: 'networkidle' }); await page.waitForNavigation({ waitUntil: 'networkidle' });
expect(page.url()).toContain(`startDelta=${startDelta}`); expect(page.url()).toContain(`startDelta=${startDelta}`);
expect(page.url()).toContain(`endDelta=${endDelta}`); expect(page.url()).toContain(`endDelta=${endDelta}`);
}); });
test.fixme('time conductor history in fixed time mode will track changing start and end times', async ({ page }) => { test.fixme(
// change start time, verify it's tracked in history 'time conductor history in fixed time mode will track changing start and end times',
// change end time, verify it's tracked in history async ({ page }) => {
}); // change start time, verify it's tracked in history
// change end time, verify it's tracked in history
}
);
test.fixme('time conductor history in realtime mode will track changing start and end times', async ({ page }) => { test.fixme(
// change start offset, verify it's tracked in history 'time conductor history in realtime mode will track changing start and end times',
// change end offset, verify it's tracked in history async ({ page }) => {
}); // change start offset, verify it's tracked in history
// change end offset, verify it's tracked in history
}
);
test.fixme('time conductor history allows you to set a historical timeframe', async ({ page }) => { test.fixme(
// make sure there are historical history options 'time conductor history allows you to set a historical timeframe',
// select an option and make sure the time conductor start and end bounds are updated correctly async ({ page }) => {
}); // make sure there are historical history options
// select an option and make sure the time conductor start and end bounds are updated correctly
}
);
test.fixme('time conductor history allows you to set a realtime offsets', async ({ page }) => { test.fixme('time conductor history allows you to set a realtime offsets', async ({ page }) => {
// make sure there are realtime history options // make sure there are realtime history options
// select an option and verify the offsets are updated correctly // select an option and verify the offsets are updated correctly
}); });
}); });
test.describe('Time Conductor History', () => { test.describe('Time Conductor History', () => {
test("shows milliseconds on hover @unstable", async ({ page }) => { test('shows milliseconds on hover @unstable', async ({ page }) => {
test.info().annotations.push({ test.info().annotations.push({
type: 'issue', type: 'issue',
description: 'https://github.com/nasa/openmct/issues/4386' description: 'https://github.com/nasa/openmct/issues/4386'
});
// Navigate to Open MCT in Fixed Time Mode, UTC Time System
// with startBound at 2022-01-01 00:00:00.000Z
// and endBound at 2022-01-01 00:00:00.200Z
await page.goto('./#/browse/mine?view=grid&tc.mode=fixed&tc.startBound=1640995200000&tc.endBound=1640995200200&tc.timeSystem=utc&hideInspector=true', { waitUntil: 'networkidle' });
await page.locator("[aria-label='Time Conductor History']").hover({ trial: true});
await page.locator("[aria-label='Time Conductor History']").click();
// Validate history item format
const historyItem = page.locator('text="2022-01-01 00:00:00 + 200ms"');
await expect(historyItem).toBeEnabled();
await expect(historyItem).toHaveAttribute('title', '2022-01-01 00:00:00.000 - 2022-01-01 00:00:00.200');
}); });
// Navigate to Open MCT in Fixed Time Mode, UTC Time System
// with startBound at 2022-01-01 00:00:00.000Z
// and endBound at 2022-01-01 00:00:00.200Z
await page.goto(
'./#/browse/mine?view=grid&tc.mode=fixed&tc.startBound=1640995200000&tc.endBound=1640995200200&tc.timeSystem=utc&hideInspector=true',
{ waitUntil: 'networkidle' }
);
await page.locator("[aria-label='Time Conductor History']").hover({ trial: true });
await page.locator("[aria-label='Time Conductor History']").click();
// Validate history item format
const historyItem = page.locator('text="2022-01-01 00:00:00 + 200ms"');
await expect(historyItem).toBeEnabled();
await expect(historyItem).toHaveAttribute(
'title',
'2022-01-01 00:00:00.000 - 2022-01-01 00:00:00.200'
);
});
}); });

View File

@ -21,43 +21,46 @@
*****************************************************************************/ *****************************************************************************/
const { test, expect } = require('../../../../pluginFixtures'); const { test, expect } = require('../../../../pluginFixtures');
const { openObjectTreeContextMenu, createDomainObjectWithDefaults } = require('../../../../appActions'); const {
openObjectTreeContextMenu,
createDomainObjectWithDefaults
} = require('../../../../appActions');
test.describe('Timer', () => { test.describe('Timer', () => {
let timer; let timer;
test.beforeEach(async ({ page }) => { test.beforeEach(async ({ page }) => {
await page.goto('./', { waitUntil: 'domcontentloaded' }); await page.goto('./', { waitUntil: 'domcontentloaded' });
timer = await createDomainObjectWithDefaults(page, { type: 'timer' }); timer = await createDomainObjectWithDefaults(page, { type: 'timer' });
});
test('Can perform actions on the Timer', async ({ page }) => {
test.info().annotations.push({
type: 'issue',
description: 'https://github.com/nasa/openmct/issues/4313'
}); });
test('Can perform actions on the Timer', async ({ page }) => { const timerUrl = timer.url;
test.info().annotations.push({
type: 'issue',
description: 'https://github.com/nasa/openmct/issues/4313'
});
const timerUrl = timer.url; await test.step('From the tree context menu', async () => {
await triggerTimerContextMenuAction(page, timerUrl, 'Start');
await test.step("From the tree context menu", async () => { await triggerTimerContextMenuAction(page, timerUrl, 'Pause');
await triggerTimerContextMenuAction(page, timerUrl, 'Start'); await triggerTimerContextMenuAction(page, timerUrl, 'Restart at 0');
await triggerTimerContextMenuAction(page, timerUrl, 'Pause'); await triggerTimerContextMenuAction(page, timerUrl, 'Stop');
await triggerTimerContextMenuAction(page, timerUrl, 'Restart at 0');
await triggerTimerContextMenuAction(page, timerUrl, 'Stop');
});
await test.step("From the 3dot menu", async () => {
await triggerTimer3dotMenuAction(page, 'Start');
await triggerTimer3dotMenuAction(page, 'Pause');
await triggerTimer3dotMenuAction(page, 'Restart at 0');
await triggerTimer3dotMenuAction(page, 'Stop');
});
await test.step("From the object view", async () => {
await triggerTimerViewAction(page, 'Start');
await triggerTimerViewAction(page, 'Pause');
await triggerTimerViewAction(page, 'Restart at 0');
});
}); });
await test.step('From the 3dot menu', async () => {
await triggerTimer3dotMenuAction(page, 'Start');
await triggerTimer3dotMenuAction(page, 'Pause');
await triggerTimer3dotMenuAction(page, 'Restart at 0');
await triggerTimer3dotMenuAction(page, 'Stop');
});
await test.step('From the object view', async () => {
await triggerTimerViewAction(page, 'Start');
await triggerTimerViewAction(page, 'Pause');
await triggerTimerViewAction(page, 'Restart at 0');
});
});
}); });
/** /**
@ -76,10 +79,10 @@ test.describe('Timer', () => {
* @param {TimerAction} action * @param {TimerAction} action
*/ */
async function triggerTimerContextMenuAction(page, timerUrl, action) { async function triggerTimerContextMenuAction(page, timerUrl, action) {
const menuAction = `.c-menu ul li >> text="${action}"`; const menuAction = `.c-menu ul li >> text="${action}"`;
await openObjectTreeContextMenu(page, timerUrl); await openObjectTreeContextMenu(page, timerUrl);
await page.locator(menuAction).click(); await page.locator(menuAction).click();
assertTimerStateAfterAction(page, action); assertTimerStateAfterAction(page, action);
} }
/** /**
@ -88,21 +91,21 @@ async function triggerTimerContextMenuAction(page, timerUrl, action) {
* @param {TimerAction} action * @param {TimerAction} action
*/ */
async function triggerTimer3dotMenuAction(page, action) { async function triggerTimer3dotMenuAction(page, action) {
const menuAction = `.c-menu ul li >> text="${action}"`; const menuAction = `.c-menu ul li >> text="${action}"`;
const threeDotMenuButton = 'button[title="More options"]'; const threeDotMenuButton = 'button[title="More options"]';
let isActionAvailable = false; let isActionAvailable = false;
let iterations = 0; let iterations = 0;
// Dismiss/open the 3dot menu until the action is available // Dismiss/open the 3dot menu until the action is available
// or a maximum number of iterations is reached // or a maximum number of iterations is reached
while (!isActionAvailable && iterations <= 20) { while (!isActionAvailable && iterations <= 20) {
await page.click('.c-object-view'); await page.click('.c-object-view');
await page.click(threeDotMenuButton); await page.click(threeDotMenuButton);
isActionAvailable = await page.locator(menuAction).isVisible(); isActionAvailable = await page.locator(menuAction).isVisible();
iterations++; iterations++;
} }
await page.locator(menuAction).click(); await page.locator(menuAction).click();
assertTimerStateAfterAction(page, action); assertTimerStateAfterAction(page, action);
} }
/** /**
@ -111,10 +114,10 @@ async function triggerTimer3dotMenuAction(page, action) {
* @param {TimerViewAction} action * @param {TimerViewAction} action
*/ */
async function triggerTimerViewAction(page, action) { async function triggerTimerViewAction(page, action) {
await page.locator('.c-timer').hover({trial: true}); await page.locator('.c-timer').hover({ trial: true });
const buttonTitle = buttonTitleFromAction(action); const buttonTitle = buttonTitleFromAction(action);
await page.click(`button[title="${buttonTitle}"]`); await page.click(`button[title="${buttonTitle}"]`);
assertTimerStateAfterAction(page, action); assertTimerStateAfterAction(page, action);
} }
/** /**
@ -122,14 +125,14 @@ async function triggerTimerViewAction(page, action) {
* @param {TimerViewAction} action * @param {TimerViewAction} action
*/ */
function buttonTitleFromAction(action) { function buttonTitleFromAction(action) {
switch (action) { switch (action) {
case 'Start': case 'Start':
return 'Start'; return 'Start';
case 'Pause': case 'Pause':
return 'Pause'; return 'Pause';
case 'Restart at 0': case 'Restart at 0':
return 'Reset'; return 'Reset';
} }
} }
/** /**
@ -138,19 +141,19 @@ function buttonTitleFromAction(action) {
* @param {TimerAction} action * @param {TimerAction} action
*/ */
async function assertTimerStateAfterAction(page, action) { async function assertTimerStateAfterAction(page, action) {
let timerStateClass; let timerStateClass;
switch (action) { switch (action) {
case 'Start': case 'Start':
case 'Restart at 0': case 'Restart at 0':
timerStateClass = "is-started"; timerStateClass = 'is-started';
break; break;
case 'Stop': case 'Stop':
timerStateClass = 'is-stopped'; timerStateClass = 'is-stopped';
break; break;
case 'Pause': case 'Pause':
timerStateClass = 'is-paused'; timerStateClass = 'is-paused';
break; break;
} }
await expect.soft(page.locator('.c-timer')).toHaveClass(new RegExp(timerStateClass)); await expect.soft(page.locator('.c-timer')).toHaveClass(new RegExp(timerStateClass));
} }

View File

@ -25,284 +25,327 @@ const { createDomainObjectWithDefaults } = require('../../appActions.js');
const { waitForAnimations } = require('../../baseFixtures.js'); const { waitForAnimations } = require('../../baseFixtures.js');
test.describe('Recent Objects', () => { test.describe('Recent Objects', () => {
/** @type {import('@playwright/test').Locator} */ /** @type {import('@playwright/test').Locator} */
let recentObjectsList; let recentObjectsList;
/** @type {import('@playwright/test').Locator} */ /** @type {import('@playwright/test').Locator} */
let clock; let clock;
/** @type {import('@playwright/test').Locator} */ /** @type {import('@playwright/test').Locator} */
let folderA; let folderA;
test.beforeEach(async ({ page }) => { test.beforeEach(async ({ page }) => {
await page.goto('./', { waitUntil: 'domcontentloaded' }); await page.goto('./', { waitUntil: 'domcontentloaded' });
// Set Recent Objects List locator for subsequent tests // Set Recent Objects List locator for subsequent tests
recentObjectsList = page.getByRole('list', { recentObjectsList = page.getByRole('list', {
name: 'Recent Objects' name: 'Recent Objects'
});
// Create a folder and nest a Clock within it
folderA = await createDomainObjectWithDefaults(page, {
type: 'Folder'
});
clock = await createDomainObjectWithDefaults(page, {
type: 'Clock',
parent: folderA.uuid
});
// Drag the Recent Objects panel up a bit
await page.locator('.l-pane.l-pane--vertical-handle-before', {
hasText: 'Recently Viewed'
}).locator('.l-pane__handle').hover();
await page.mouse.down();
await page.mouse.move(0, 100);
await page.mouse.up();
});
test('Navigated objects show up in recents, object renames and deletions are reflected', async ({ page }) => {
// Verify that both created objects appear in the list and are in the correct order
await assertInitialRecentObjectsListState();
// Navigate to the folder by clicking on the main object name in the recent objects list item
await page.getByRole('listitem', { name: folderA.name }).getByText(folderA.name).click();
await page.waitForURL(`**/${folderA.uuid}?*`);
expect(recentObjectsList.getByRole('listitem').nth(0).getByText(folderA.name)).toBeTruthy();
// Rename
folderA.name = `${folderA.name}-NEW!`;
await page.locator('.l-browse-bar__object-name').fill("");
await page.locator('.l-browse-bar__object-name').fill(folderA.name);
await page.keyboard.press('Enter');
// Verify rename has been applied in recent objects list item and objects paths
expect(await page.getByRole('navigation', {
name: clock.name
}).locator('a').filter({
hasText: folderA.name
}).count()).toBeGreaterThan(0);
expect(recentObjectsList.getByRole('listitem', { name: folderA.name })).toBeTruthy();
// Delete
await page.click('button[title="Show selected item in tree"]');
// Delete the folder via the left tree pane treeitem context menu
await page.getByRole('treeitem', { name: new RegExp(folderA.name) }).locator('a').click({
button: 'right'
});
await page.getByRole('menuitem', { name: /Remove/ }).click();
await page.getByRole('button', { name: 'OK' }).click();
// Verify that the folder and clock are no longer in the recent objects list
await expect(recentObjectsList.getByRole('listitem', { name: folderA.name })).toBeHidden();
await expect(recentObjectsList.getByRole('listitem', { name: clock.name })).toBeHidden();
});
test("Clicking on an object in the path of a recent object navigates to the object", async ({ page, openmctConfig }) => {
const { myItemsFolderName } = openmctConfig;
test.info().annotations.push({
type: 'issue',
description: 'https://github.com/nasa/openmct/issues/6151'
});
await page.goto('./#/browse/mine');
// Navigate to the folder by clicking on its entry in the Clock's breadcrumb
const waitForFolderNavigation = page.waitForURL(`**/${folderA.uuid}?*`);
await page.getByRole('navigation', {
name: clock.name
}).locator('a').filter({
hasText: folderA.name
}).click();
// Verify that the hash URL updates correctly
await waitForFolderNavigation;
expect(page.url()).toMatch(new RegExp(`.*${folderA.uuid}?.*`));
// Navigate to My Items by clicking on its entry in the Clock's breadcrumb
const waitForMyItemsNavigation = page.waitForURL(`**/mine?*`);
await page.getByRole('navigation', {
name: clock.name
}).locator('a').filter({
hasText: myItemsFolderName
}).click();
// Verify that the hash URL updates correctly
await waitForMyItemsNavigation;
expect(page.url()).toMatch(new RegExp(`.*mine?.*`));
});
test("Clicking on the 'target button' scrolls the object into view in the tree and highlights it", async ({ page }) => {
const clockTreeItem = page.getByRole('tree', { name: 'Main Tree'}).getByRole('treeitem', { name: clock.name });
const folderTreeItem = page.getByRole('tree', { name: 'Main Tree'})
.getByRole('treeitem', {
name: folderA.name,
expanded: true
});
// Click the "Target" button for the Clock which is nested in a folder
await page.getByRole('button', { name: `Open and scroll to ${clock.name}`}).click();
// Assert that the Clock parent folder has expanded and the Clock is visible)
await expect(folderTreeItem.locator('.c-disclosure-triangle')).toHaveClass(/--expanded/);
await expect(clockTreeItem).toBeVisible();
// Assert that the Clock treeitem is highlighted
await expect(clockTreeItem.locator('.c-tree__item')).toHaveClass(/is-targeted-item/);
// Wait for highlight animation to end
await waitForAnimations(clockTreeItem.locator('.c-tree__item'));
// Assert that the Clock treeitem is no longer highlighted
await expect(clockTreeItem.locator('.c-tree__item')).not.toHaveClass(/is-targeted-item/);
});
test("Persists on refresh", async ({ page }) => {
await assertInitialRecentObjectsListState();
await page.reload();
await assertInitialRecentObjectsListState();
});
test("Displays objects and aliases uniquely", async ({ page }) => {
const mainTree = page.getByRole('tree', { name: 'Main Tree'});
// Navigate to the clock and reveal it in the tree
await page.goto(clock.url);
await page.getByTitle('Show selected item in tree').click();
// Right click the clock and create an alias using the "link" context menu action
const clockTreeItem = page.getByRole('tree', {
name: 'Main Tree'
}).getByRole('treeitem', {
name: clock.name
});
await clockTreeItem.click({
button: 'right'
});
await page.getByRole('menuitem', {
name: /Create Link/
}).click();
await page.getByRole('tree', { name: 'Create Modal Tree'}).getByRole('treeitem').first().click();
await page.getByRole('button', { name: 'Save' }).click();
// Click the newly created object alias in the tree
await mainTree.getByRole('treeitem', {
name: new RegExp(clock.name)
}).filter({
has: page.locator('.is-alias')
}).click();
// Assert that two recent objects are displayed and one of them is an alias
expect(await recentObjectsList.getByRole('listitem', { name: clock.name }).count()).toBe(2);
expect(await recentObjectsList.locator('.is-alias').count()).toBe(1);
// Assert that the alias and the original's breadcrumbs are different
const clockBreadcrumbs = recentObjectsList.getByRole('listitem', {name: clock.name}).getByRole('navigation');
expect(await clockBreadcrumbs.count()).toBe(2);
expect(await clockBreadcrumbs.nth(0).innerText()).not.toEqual(await clockBreadcrumbs.nth(1).innerText());
});
test("Enforces a limit of 20 recent objects and clears the recent objects", async ({ page }) => {
// Creating 21 objects takes a while, so increase the timeout
test.slow();
// Assert that the list initially contains 3 objects (clock, folder, my items)
expect(await recentObjectsList.locator('.c-recentobjects-listitem').count()).toBe(3);
let lastFolder;
let lastClock;
// Create 19 more objects (3 in beforeEach() + 18 new = 21 total)
for (let i = 0; i < 9; i++) {
lastFolder = await createDomainObjectWithDefaults(page, {
type: "Folder",
parent: lastFolder?.uuid
});
lastClock = await createDomainObjectWithDefaults(page, {
type: "Clock",
parent: lastFolder?.uuid
});
}
// Assert that the list contains 20 objects
expect(await recentObjectsList.locator('.c-recentobjects-listitem').count()).toBe(20);
// Collapse the tree
await page.getByTitle("Collapse all tree items").click();
const lastFolderTreeItem = page.getByRole('tree', { name: 'Main Tree'})
.getByRole('treeitem', {
name: lastFolder.name,
expanded: true
});
const lastClockTreeItem = page.getByRole('tree', { name: 'Main Tree'})
.getByRole('treeitem', {
name: lastClock.name
});
// Test "Open and Scroll To" in a deeply nested tree, while we're here
await page.getByRole('button', { name: `Open and scroll to ${lastClock.name}`}).click();
// Assert that the Clock parent folder has expanded and the Clock is visible)
await expect(lastFolderTreeItem.locator('.c-disclosure-triangle')).toHaveClass(/--expanded/);
await expect(lastClockTreeItem).toBeVisible();
// Assert that the Clock treeitem is highlighted
await expect(lastClockTreeItem.locator('.c-tree__item')).toHaveClass(/is-targeted-item/);
// Wait for highlight animation to end
await waitForAnimations(lastClockTreeItem.locator('.c-tree__item'));
// Assert that the Clock treeitem is no longer highlighted
await expect(lastClockTreeItem.locator('.c-tree__item')).not.toHaveClass(/is-targeted-item/);
// Click the aria-label="Clear Recently Viewed" button
await page.getByRole('button', { name: 'Clear Recently Viewed' }).click();
// Click on the "OK" button in the confirmation dialog
await page.getByRole('button', { name: 'OK' }).click();
// Assert that the list is empty
expect(await recentObjectsList.locator('.c-recentobjects-listitem').count()).toBe(0);
});
test("Ensure clear recent objects button is active or inactive", async ({ page }) => {
// Assert that the list initially contains 3 objects (clock, folder, my items)
expect(await recentObjectsList.locator('.c-recentobjects-listitem').count()).toBe(3);
// Assert that the button is enabled
expect(
await page
.getByRole("button", { name: "Clear Recently Viewed" })
.isEnabled()
).toBe(true);
// Click the aria-label="Clear Recently Viewed" button
await page.getByRole("button", { name: "Clear Recently Viewed" }).click();
// Click on the "OK" button in the confirmation dialog
await page.getByRole("button", { name: "OK" }).click();
// Assert that the list is empty
expect(
await recentObjectsList.locator(".c-recentobjects-listitem").count()
).toBe(0);
// Assert that the button is disabled
expect(
await page
.getByRole("button", { name: "Clear Recently Viewed" })
.isEnabled()
).toBe(false);
// Navigate to folder object
await page.goto(folderA.url);
// Assert that the list contains 1 object
expect(await recentObjectsList.locator('.c-recentobjects-listitem').count()).toBe(1);
// Assert that the button is enabled
expect(
await page
.getByRole("button", { name: "Clear Recently Viewed" })
.isEnabled()
).toBe(true);
}); });
function assertInitialRecentObjectsListState() { // Create a folder and nest a Clock within it
return Promise.all([ folderA = await createDomainObjectWithDefaults(page, {
expect(recentObjectsList.getByRole('listitem', { name: clock.name })).toBeVisible(), type: 'Folder'
expect(recentObjectsList.getByRole('listitem', { name: folderA.name })).toBeVisible(), });
expect(recentObjectsList.getByRole('listitem', { name: clock.name }).locator('a').getByText(folderA.name)).toBeVisible(), clock = await createDomainObjectWithDefaults(page, {
expect(recentObjectsList.getByRole('listitem').nth(0).getByText(clock.name)).toBeVisible(), type: 'Clock',
expect(recentObjectsList.getByRole('listitem', { name: clock.name }).locator('a').getByText(folderA.name)).toBeVisible(), parent: folderA.uuid
expect(recentObjectsList.getByRole('listitem').nth(3).getByText(folderA.name)).toBeVisible() });
]);
// Drag the Recent Objects panel up a bit
await page
.locator('.l-pane.l-pane--vertical-handle-before', {
hasText: 'Recently Viewed'
})
.locator('.l-pane__handle')
.hover();
await page.mouse.down();
await page.mouse.move(0, 100);
await page.mouse.up();
});
test('Navigated objects show up in recents, object renames and deletions are reflected', async ({
page
}) => {
// Verify that both created objects appear in the list and are in the correct order
await assertInitialRecentObjectsListState();
// Navigate to the folder by clicking on the main object name in the recent objects list item
await page.getByRole('listitem', { name: folderA.name }).getByText(folderA.name).click();
await page.waitForURL(`**/${folderA.uuid}?*`);
expect(recentObjectsList.getByRole('listitem').nth(0).getByText(folderA.name)).toBeTruthy();
// Rename
folderA.name = `${folderA.name}-NEW!`;
await page.locator('.l-browse-bar__object-name').fill('');
await page.locator('.l-browse-bar__object-name').fill(folderA.name);
await page.keyboard.press('Enter');
// Verify rename has been applied in recent objects list item and objects paths
expect(
await page
.getByRole('navigation', {
name: clock.name
})
.locator('a')
.filter({
hasText: folderA.name
})
.count()
).toBeGreaterThan(0);
expect(recentObjectsList.getByRole('listitem', { name: folderA.name })).toBeTruthy();
// Delete
await page.click('button[title="Show selected item in tree"]');
// Delete the folder via the left tree pane treeitem context menu
await page
.getByRole('treeitem', { name: new RegExp(folderA.name) })
.locator('a')
.click({
button: 'right'
});
await page.getByRole('menuitem', { name: /Remove/ }).click();
await page.getByRole('button', { name: 'OK' }).click();
// Verify that the folder and clock are no longer in the recent objects list
await expect(recentObjectsList.getByRole('listitem', { name: folderA.name })).toBeHidden();
await expect(recentObjectsList.getByRole('listitem', { name: clock.name })).toBeHidden();
});
test('Clicking on an object in the path of a recent object navigates to the object', async ({
page,
openmctConfig
}) => {
const { myItemsFolderName } = openmctConfig;
test.info().annotations.push({
type: 'issue',
description: 'https://github.com/nasa/openmct/issues/6151'
});
await page.goto('./#/browse/mine');
// Navigate to the folder by clicking on its entry in the Clock's breadcrumb
const waitForFolderNavigation = page.waitForURL(`**/${folderA.uuid}?*`);
await page
.getByRole('navigation', {
name: clock.name
})
.locator('a')
.filter({
hasText: folderA.name
})
.click();
// Verify that the hash URL updates correctly
await waitForFolderNavigation;
expect(page.url()).toMatch(new RegExp(`.*${folderA.uuid}?.*`));
// Navigate to My Items by clicking on its entry in the Clock's breadcrumb
const waitForMyItemsNavigation = page.waitForURL(`**/mine?*`);
await page
.getByRole('navigation', {
name: clock.name
})
.locator('a')
.filter({
hasText: myItemsFolderName
})
.click();
// Verify that the hash URL updates correctly
await waitForMyItemsNavigation;
expect(page.url()).toMatch(new RegExp(`.*mine?.*`));
});
test("Clicking on the 'target button' scrolls the object into view in the tree and highlights it", async ({
page
}) => {
const clockTreeItem = page
.getByRole('tree', { name: 'Main Tree' })
.getByRole('treeitem', { name: clock.name });
const folderTreeItem = page.getByRole('tree', { name: 'Main Tree' }).getByRole('treeitem', {
name: folderA.name,
expanded: true
});
// Click the "Target" button for the Clock which is nested in a folder
await page.getByRole('button', { name: `Open and scroll to ${clock.name}` }).click();
// Assert that the Clock parent folder has expanded and the Clock is visible)
await expect(folderTreeItem.locator('.c-disclosure-triangle')).toHaveClass(/--expanded/);
await expect(clockTreeItem).toBeVisible();
// Assert that the Clock treeitem is highlighted
await expect(clockTreeItem.locator('.c-tree__item')).toHaveClass(/is-targeted-item/);
// Wait for highlight animation to end
await waitForAnimations(clockTreeItem.locator('.c-tree__item'));
// Assert that the Clock treeitem is no longer highlighted
await expect(clockTreeItem.locator('.c-tree__item')).not.toHaveClass(/is-targeted-item/);
});
test('Persists on refresh', async ({ page }) => {
await assertInitialRecentObjectsListState();
await page.reload();
await assertInitialRecentObjectsListState();
});
test('Displays objects and aliases uniquely', async ({ page }) => {
const mainTree = page.getByRole('tree', { name: 'Main Tree' });
// Navigate to the clock and reveal it in the tree
await page.goto(clock.url);
await page.getByTitle('Show selected item in tree').click();
// Right click the clock and create an alias using the "link" context menu action
const clockTreeItem = page
.getByRole('tree', {
name: 'Main Tree'
})
.getByRole('treeitem', {
name: clock.name
});
await clockTreeItem.click({
button: 'right'
});
await page
.getByRole('menuitem', {
name: /Create Link/
})
.click();
await page
.getByRole('tree', { name: 'Create Modal Tree' })
.getByRole('treeitem')
.first()
.click();
await page.getByRole('button', { name: 'Save' }).click();
// Click the newly created object alias in the tree
await mainTree
.getByRole('treeitem', {
name: new RegExp(clock.name)
})
.filter({
has: page.locator('.is-alias')
})
.click();
// Assert that two recent objects are displayed and one of them is an alias
expect(await recentObjectsList.getByRole('listitem', { name: clock.name }).count()).toBe(2);
expect(await recentObjectsList.locator('.is-alias').count()).toBe(1);
// Assert that the alias and the original's breadcrumbs are different
const clockBreadcrumbs = recentObjectsList
.getByRole('listitem', { name: clock.name })
.getByRole('navigation');
expect(await clockBreadcrumbs.count()).toBe(2);
expect(await clockBreadcrumbs.nth(0).innerText()).not.toEqual(
await clockBreadcrumbs.nth(1).innerText()
);
});
test('Enforces a limit of 20 recent objects and clears the recent objects', async ({ page }) => {
// Creating 21 objects takes a while, so increase the timeout
test.slow();
// Assert that the list initially contains 3 objects (clock, folder, my items)
expect(await recentObjectsList.locator('.c-recentobjects-listitem').count()).toBe(3);
let lastFolder;
let lastClock;
// Create 19 more objects (3 in beforeEach() + 18 new = 21 total)
for (let i = 0; i < 9; i++) {
lastFolder = await createDomainObjectWithDefaults(page, {
type: 'Folder',
parent: lastFolder?.uuid
});
lastClock = await createDomainObjectWithDefaults(page, {
type: 'Clock',
parent: lastFolder?.uuid
});
} }
// Assert that the list contains 20 objects
expect(await recentObjectsList.locator('.c-recentobjects-listitem').count()).toBe(20);
// Collapse the tree
await page.getByTitle('Collapse all tree items').click();
const lastFolderTreeItem = page.getByRole('tree', { name: 'Main Tree' }).getByRole('treeitem', {
name: lastFolder.name,
expanded: true
});
const lastClockTreeItem = page.getByRole('tree', { name: 'Main Tree' }).getByRole('treeitem', {
name: lastClock.name
});
// Test "Open and Scroll To" in a deeply nested tree, while we're here
await page.getByRole('button', { name: `Open and scroll to ${lastClock.name}` }).click();
// Assert that the Clock parent folder has expanded and the Clock is visible)
await expect(lastFolderTreeItem.locator('.c-disclosure-triangle')).toHaveClass(/--expanded/);
await expect(lastClockTreeItem).toBeVisible();
// Assert that the Clock treeitem is highlighted
await expect(lastClockTreeItem.locator('.c-tree__item')).toHaveClass(/is-targeted-item/);
// Wait for highlight animation to end
await waitForAnimations(lastClockTreeItem.locator('.c-tree__item'));
// Assert that the Clock treeitem is no longer highlighted
await expect(lastClockTreeItem.locator('.c-tree__item')).not.toHaveClass(/is-targeted-item/);
// Click the aria-label="Clear Recently Viewed" button
await page.getByRole('button', { name: 'Clear Recently Viewed' }).click();
// Click on the "OK" button in the confirmation dialog
await page.getByRole('button', { name: 'OK' }).click();
// Assert that the list is empty
expect(await recentObjectsList.locator('.c-recentobjects-listitem').count()).toBe(0);
});
test('Ensure clear recent objects button is active or inactive', async ({ page }) => {
// Assert that the list initially contains 3 objects (clock, folder, my items)
expect(await recentObjectsList.locator('.c-recentobjects-listitem').count()).toBe(3);
// Assert that the button is enabled
expect(await page.getByRole('button', { name: 'Clear Recently Viewed' }).isEnabled()).toBe(
true
);
// Click the aria-label="Clear Recently Viewed" button
await page.getByRole('button', { name: 'Clear Recently Viewed' }).click();
// Click on the "OK" button in the confirmation dialog
await page.getByRole('button', { name: 'OK' }).click();
// Assert that the list is empty
expect(await recentObjectsList.locator('.c-recentobjects-listitem').count()).toBe(0);
// Assert that the button is disabled
expect(await page.getByRole('button', { name: 'Clear Recently Viewed' }).isEnabled()).toBe(
false
);
// Navigate to folder object
await page.goto(folderA.url);
// Assert that the list contains 1 object
expect(await recentObjectsList.locator('.c-recentobjects-listitem').count()).toBe(1);
// Assert that the button is enabled
expect(await page.getByRole('button', { name: 'Clear Recently Viewed' }).isEnabled()).toBe(
true
);
});
function assertInitialRecentObjectsListState() {
return Promise.all([
expect(recentObjectsList.getByRole('listitem', { name: clock.name })).toBeVisible(),
expect(recentObjectsList.getByRole('listitem', { name: folderA.name })).toBeVisible(),
expect(
recentObjectsList
.getByRole('listitem', { name: clock.name })
.locator('a')
.getByText(folderA.name)
).toBeVisible(),
expect(recentObjectsList.getByRole('listitem').nth(0).getByText(clock.name)).toBeVisible(),
expect(
recentObjectsList
.getByRole('listitem', { name: clock.name })
.locator('a')
.getByText(folderA.name)
).toBeVisible(),
expect(recentObjectsList.getByRole('listitem').nth(3).getByText(folderA.name)).toBeVisible()
]);
}
}); });

View File

@ -28,242 +28,270 @@ const { createDomainObjectWithDefaults, selectInspectorTab } = require('../../ap
const { v4: uuid } = require('uuid'); const { v4: uuid } = require('uuid');
test.describe('Grand Search', () => { test.describe('Grand Search', () => {
const searchResultSelector = '.c-gsearch-result__title'; const searchResultSelector = '.c-gsearch-result__title';
const searchResultDropDownSelector = '.c-gsearch__results'; const searchResultDropDownSelector = '.c-gsearch__results';
test.beforeEach(async ({ page }) => { test.beforeEach(async ({ page }) => {
// Go to baseURL // Go to baseURL
await page.goto("./", { waitUntil: "networkidle" }); await page.goto('./', { waitUntil: 'networkidle' });
});
test('Can search for objects, and subsequent search dropdown behaves properly', async ({
page,
openmctConfig
}) => {
const { myItemsFolderName } = openmctConfig;
const createdObjects = await createObjectsForSearch(page);
// Click [aria-label="OpenMCT Search"] input[type="search"]
await page.locator('[aria-label="OpenMCT Search"] input[type="search"]').click();
// Fill [aria-label="OpenMCT Search"] input[type="search"]
await page.locator('[aria-label="OpenMCT Search"] input[type="search"]').fill('Cl');
await expect(page.locator('[aria-label="Search Result"] >> nth=0')).toContainText(
`Clock A ${myItemsFolderName} Red Folder Blue Folder`
);
await expect(page.locator('[aria-label="Search Result"] >> nth=1')).toContainText(
`Clock B ${myItemsFolderName} Red Folder Blue Folder`
);
await expect(page.locator('[aria-label="Search Result"] >> nth=2')).toContainText(
`Clock C ${myItemsFolderName} Red Folder Blue Folder`
);
await expect(page.locator('[aria-label="Search Result"] >> nth=3')).toContainText(
`Clock D ${myItemsFolderName} Red Folder Blue Folder`
);
// Click the Elements pool to dismiss the search menu
await selectInspectorTab(page, 'Elements');
await expect(page.locator('[aria-label="Search Result"] >> nth=0')).toBeHidden();
await page.locator('[aria-label="OpenMCT Search"] [aria-label="Search Input"]').click();
await page.locator('[aria-label="Clock A clock result"] >> text=Clock A').click();
await expect(page.locator('.js-preview-window')).toBeVisible();
// Click [aria-label="Close"]
await page.locator('[aria-label="Close"]').click();
await expect(page.locator('[aria-label="Search Result"] >> nth=0')).toBeVisible();
await expect(page.locator('[aria-label="Search Result"] >> nth=0')).toContainText(
`Clock A ${myItemsFolderName} Red Folder Blue Folder`
);
// Click [aria-label="OpenMCT Search"] a >> nth=0
await page.locator('[aria-label="Search Result"] >> nth=0').click();
await expect(page.locator('[aria-label="Search Result"] >> nth=0')).toBeHidden();
// Fill [aria-label="OpenMCT Search"] input[type="search"]
await page.locator('[aria-label="OpenMCT Search"] input[type="search"]').fill('foo');
await expect(page.locator('[aria-label="Search Result"] >> nth=0')).toBeHidden();
// Click text=Snapshot Save and Finish Editing Save and Continue Editing >> button >> nth=1
await page
.locator('text=Snapshot Save and Finish Editing Save and Continue Editing >> button')
.nth(1)
.click();
// Click text=Save and Finish Editing
await page.locator('text=Save and Finish Editing').click();
// Click [aria-label="OpenMCT Search"] [aria-label="Search Input"]
await page.locator('[aria-label="OpenMCT Search"] [aria-label="Search Input"]').click();
// Fill [aria-label="OpenMCT Search"] [aria-label="Search Input"]
await page.locator('[aria-label="OpenMCT Search"] [aria-label="Search Input"]').fill('Cl');
await Promise.all([
page.waitForNavigation(),
page.locator('[aria-label="Clock A clock result"] >> text=Clock A').click()
]);
await expect(page.locator('.is-object-type-clock')).toBeVisible();
await page.locator('[aria-label="OpenMCT Search"] [aria-label="Search Input"]').fill('Disp');
await expect(page.locator('[aria-label="Search Result"] >> nth=0')).toContainText(
createdObjects.displayLayout.name
);
await expect(page.locator('[aria-label="Search Result"] >> nth=0')).not.toContainText('Folder');
await page.locator('[aria-label="OpenMCT Search"] input[type="search"]').fill('Clock C');
await expect(page.locator('[aria-label="Search Result"] >> nth=0')).toContainText(
`Clock C ${myItemsFolderName} Red Folder Blue Folder`
);
await page.locator('[aria-label="OpenMCT Search"] input[type="search"]').fill('Cloc');
await expect(page.locator('[aria-label="Search Result"] >> nth=0')).toContainText(
`Clock A ${myItemsFolderName} Red Folder Blue Folder`
);
await expect(page.locator('[aria-label="Search Result"] >> nth=1')).toContainText(
`Clock B ${myItemsFolderName} Red Folder Blue Folder`
);
await expect(page.locator('[aria-label="Search Result"] >> nth=2')).toContainText(
`Clock C ${myItemsFolderName} Red Folder Blue Folder`
);
await expect(page.locator('[aria-label="Search Result"] >> nth=3')).toContainText(
`Clock D ${myItemsFolderName} Red Folder Blue Folder`
);
});
test('Validate empty search result', async ({ page }) => {
// Invalid search for objects
await page.type('input[type=search]', 'not found');
// Wait for search to complete
await waitForSearchCompletion(page);
// Get the search results
const searchResults = page.locator(searchResultSelector);
// Verify that no results are found
expect(await searchResults.count()).toBe(0);
// Verify proper message appears
await expect(page.locator('text=No results found')).toBeVisible();
});
test('Validate single object in search result @couchdb', async ({ page }) => {
// Create a folder object
const folderName = uuid();
await createDomainObjectWithDefaults(page, {
type: 'folder',
name: folderName
}); });
test('Can search for objects, and subsequent search dropdown behaves properly', async ({ page, openmctConfig }) => { // Full search for object
const { myItemsFolderName } = openmctConfig; await page.type('input[type=search]', folderName);
const createdObjects = await createObjectsForSearch(page); // Wait for search to complete
await waitForSearchCompletion(page);
// Click [aria-label="OpenMCT Search"] input[type="search"] // Get the search results
await page.locator('[aria-label="OpenMCT Search"] input[type="search"]').click(); const searchResults = page.locator(searchResultSelector);
// Fill [aria-label="OpenMCT Search"] input[type="search"]
await page.locator('[aria-label="OpenMCT Search"] input[type="search"]').fill('Cl');
await expect(page.locator('[aria-label="Search Result"] >> nth=0')).toContainText(`Clock A ${myItemsFolderName} Red Folder Blue Folder`);
await expect(page.locator('[aria-label="Search Result"] >> nth=1')).toContainText(`Clock B ${myItemsFolderName} Red Folder Blue Folder`);
await expect(page.locator('[aria-label="Search Result"] >> nth=2')).toContainText(`Clock C ${myItemsFolderName} Red Folder Blue Folder`);
await expect(page.locator('[aria-label="Search Result"] >> nth=3')).toContainText(`Clock D ${myItemsFolderName} Red Folder Blue Folder`);
// Click the Elements pool to dismiss the search menu
await selectInspectorTab(page, 'Elements');
await expect(page.locator('[aria-label="Search Result"] >> nth=0')).toBeHidden();
await page.locator('[aria-label="OpenMCT Search"] [aria-label="Search Input"]').click(); // Verify that one result is found
await page.locator('[aria-label="Clock A clock result"] >> text=Clock A').click(); await expect(searchResults).toBeVisible();
await expect(page.locator('.js-preview-window')).toBeVisible(); expect(await searchResults.count()).toBe(1);
await expect(searchResults).toHaveText(folderName);
});
// Click [aria-label="Close"] test('Search results are debounced @couchdb', async ({ page }) => {
await page.locator('[aria-label="Close"]').click(); test.info().annotations.push({
await expect(page.locator('[aria-label="Search Result"] >> nth=0')).toBeVisible(); type: 'issue',
await expect(page.locator('[aria-label="Search Result"] >> nth=0')).toContainText(`Clock A ${myItemsFolderName} Red Folder Blue Folder`); description: 'https://github.com/nasa/openmct/issues/6179'
});
await createObjectsForSearch(page);
// Click [aria-label="OpenMCT Search"] a >> nth=0 let networkRequests = [];
await page.locator('[aria-label="Search Result"] >> nth=0').click(); page.on('request', (request) => {
await expect(page.locator('[aria-label="Search Result"] >> nth=0')).toBeHidden(); const searchRequest = request.url().endsWith('_find');
const fetchRequest = request.resourceType() === 'fetch';
// Fill [aria-label="OpenMCT Search"] input[type="search"] if (searchRequest && fetchRequest) {
await page.locator('[aria-label="OpenMCT Search"] input[type="search"]').fill('foo'); networkRequests.push(request);
await expect(page.locator('[aria-label="Search Result"] >> nth=0')).toBeHidden(); }
// Click text=Snapshot Save and Finish Editing Save and Continue Editing >> button >> nth=1
await page.locator('text=Snapshot Save and Finish Editing Save and Continue Editing >> button').nth(1).click();
// Click text=Save and Finish Editing
await page.locator('text=Save and Finish Editing').click();
// Click [aria-label="OpenMCT Search"] [aria-label="Search Input"]
await page.locator('[aria-label="OpenMCT Search"] [aria-label="Search Input"]').click();
// Fill [aria-label="OpenMCT Search"] [aria-label="Search Input"]
await page.locator('[aria-label="OpenMCT Search"] [aria-label="Search Input"]').fill('Cl');
await Promise.all([
page.waitForNavigation(),
page.locator('[aria-label="Clock A clock result"] >> text=Clock A').click()
]);
await expect(page.locator('.is-object-type-clock')).toBeVisible();
await page.locator('[aria-label="OpenMCT Search"] [aria-label="Search Input"]').fill('Disp');
await expect(page.locator('[aria-label="Search Result"] >> nth=0')).toContainText(createdObjects.displayLayout.name);
await expect(page.locator('[aria-label="Search Result"] >> nth=0')).not.toContainText('Folder');
await page.locator('[aria-label="OpenMCT Search"] input[type="search"]').fill('Clock C');
await expect(page.locator('[aria-label="Search Result"] >> nth=0')).toContainText(`Clock C ${myItemsFolderName} Red Folder Blue Folder`);
await page.locator('[aria-label="OpenMCT Search"] input[type="search"]').fill('Cloc');
await expect(page.locator('[aria-label="Search Result"] >> nth=0')).toContainText(`Clock A ${myItemsFolderName} Red Folder Blue Folder`);
await expect(page.locator('[aria-label="Search Result"] >> nth=1')).toContainText(`Clock B ${myItemsFolderName} Red Folder Blue Folder`);
await expect(page.locator('[aria-label="Search Result"] >> nth=2')).toContainText(`Clock C ${myItemsFolderName} Red Folder Blue Folder`);
await expect(page.locator('[aria-label="Search Result"] >> nth=3')).toContainText(`Clock D ${myItemsFolderName} Red Folder Blue Folder`);
}); });
test('Validate empty search result', async ({ page }) => { // Full search for object
// Invalid search for objects await page.type('input[type=search]', 'Clock', { delay: 100 });
await page.type("input[type=search]", 'not found');
// Wait for search to complete // Wait for search to finish
await waitForSearchCompletion(page); await waitForSearchCompletion(page);
// Get the search results // Network requests for the composite telemetry with multiple items should be:
const searchResults = page.locator(searchResultSelector); // 1. batched request for latest telemetry using the bulk API
expect(networkRequests.length).toBe(1);
// Verify that no results are found const searchResultDropDown = await page.locator(searchResultDropDownSelector);
expect(await searchResults.count()).toBe(0);
// Verify proper message appears await expect(searchResultDropDown).toContainText('Clock A');
await expect(page.locator('text=No results found')).toBeVisible(); });
test('Validate multiple objects in search results return partial matches', async ({ page }) => {
test.info().annotations.push({
type: 'issue',
description: 'https://github.com/nasa/openmct/issues/4667'
}); });
test('Validate single object in search result @couchdb', async ({ page }) => { // Create folder objects
// Create a folder object const folderName1 = 'e928a26e-e924-4ea0';
const folderName = uuid(); const folderName2 = 'e928a26e-e924-4001';
await createDomainObjectWithDefaults(page, {
type: 'folder',
name: folderName
});
// Full search for object await createDomainObjectWithDefaults(page, {
await page.type("input[type=search]", folderName); type: 'Folder',
name: folderName1
// Wait for search to complete
await waitForSearchCompletion(page);
// Get the search results
const searchResults = page.locator(searchResultSelector);
// Verify that one result is found
await expect(searchResults).toBeVisible();
expect(await searchResults.count()).toBe(1);
await expect(searchResults).toHaveText(folderName);
}); });
test('Search results are debounced @couchdb', async ({ page }) => { await createDomainObjectWithDefaults(page, {
test.info().annotations.push({ type: 'Folder',
type: 'issue', name: folderName2
description: 'https://github.com/nasa/openmct/issues/6179'
});
await createObjectsForSearch(page);
let networkRequests = [];
page.on('request', (request) => {
const searchRequest = request.url().endsWith('_find');
const fetchRequest = request.resourceType() === 'fetch';
if (searchRequest && fetchRequest) {
networkRequests.push(request);
}
});
// Full search for object
await page.type("input[type=search]", 'Clock', { delay: 100 });
// Wait for search to finish
await waitForSearchCompletion(page);
// Network requests for the composite telemetry with multiple items should be:
// 1. batched request for latest telemetry using the bulk API
expect(networkRequests.length).toBe(1);
const searchResultDropDown = await page.locator(searchResultDropDownSelector);
await expect(searchResultDropDown).toContainText('Clock A');
}); });
test("Validate multiple objects in search results return partial matches", async ({ page }) => { // Partial search for objects
test.info().annotations.push({ await page.type('input[type=search]', 'e928a26e');
type: 'issue',
description: 'https://github.com/nasa/openmct/issues/4667'
});
// Create folder objects // Wait for search to finish
const folderName1 = "e928a26e-e924-4ea0"; await waitForSearchCompletion(page);
const folderName2 = "e928a26e-e924-4001";
await createDomainObjectWithDefaults(page, { const searchResultDropDown = page.locator(searchResultDropDownSelector);
type: 'Folder',
name: folderName1
});
await createDomainObjectWithDefaults(page, { // Verify that the search result/s correctly match the search query
type: 'Folder', await expect(searchResultDropDown).toContainText(folderName1);
name: folderName2 await expect(searchResultDropDown).toContainText(folderName2);
});
// Partial search for objects // Get the search results
await page.type("input[type=search]", 'e928a26e'); const searchResults = page.locator(searchResultSelector);
// Verify that two results are found
// Wait for search to finish expect(await searchResults.count()).toBe(2);
await waitForSearchCompletion(page); });
const searchResultDropDown = page.locator(searchResultDropDownSelector);
// Verify that the search result/s correctly match the search query
await expect(searchResultDropDown).toContainText(folderName1);
await expect(searchResultDropDown).toContainText(folderName2);
// Get the search results
const searchResults = page.locator(searchResultSelector);
// Verify that two results are found
expect(await searchResults.count()).toBe(2);
});
}); });
async function waitForSearchCompletion(page) { async function waitForSearchCompletion(page) {
// Wait loading spinner to disappear // Wait loading spinner to disappear
await page.waitForSelector('.search-finished'); await page.waitForSelector('.search-finished');
} }
/** /**
* Creates some domain objects for searching * Creates some domain objects for searching
* @param {import('@playwright/test').Page} page * @param {import('@playwright/test').Page} page
*/ */
async function createObjectsForSearch(page) { async function createObjectsForSearch(page) {
const redFolder = await createDomainObjectWithDefaults(page, { const redFolder = await createDomainObjectWithDefaults(page, {
type: 'Folder', type: 'Folder',
name: 'Red Folder' name: 'Red Folder'
}); });
const blueFolder = await createDomainObjectWithDefaults(page, { const blueFolder = await createDomainObjectWithDefaults(page, {
type: 'Folder', type: 'Folder',
name: 'Blue Folder', name: 'Blue Folder',
parent: redFolder.uuid parent: redFolder.uuid
}); });
const clockA = await createDomainObjectWithDefaults(page, { const clockA = await createDomainObjectWithDefaults(page, {
type: 'Clock', type: 'Clock',
name: 'Clock A', name: 'Clock A',
parent: blueFolder.uuid parent: blueFolder.uuid
}); });
const clockB = await createDomainObjectWithDefaults(page, { const clockB = await createDomainObjectWithDefaults(page, {
type: 'Clock', type: 'Clock',
name: 'Clock B', name: 'Clock B',
parent: blueFolder.uuid parent: blueFolder.uuid
}); });
const clockC = await createDomainObjectWithDefaults(page, { const clockC = await createDomainObjectWithDefaults(page, {
type: 'Clock', type: 'Clock',
name: 'Clock C', name: 'Clock C',
parent: blueFolder.uuid parent: blueFolder.uuid
}); });
const clockD = await createDomainObjectWithDefaults(page, { const clockD = await createDomainObjectWithDefaults(page, {
type: 'Clock', type: 'Clock',
name: 'Clock D', name: 'Clock D',
parent: blueFolder.uuid parent: blueFolder.uuid
}); });
const displayLayout = await createDomainObjectWithDefaults(page, { const displayLayout = 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.locator('button[title="Edit"]').click();
return { return {
redFolder, redFolder,
blueFolder, blueFolder,
clockA, clockA,
clockB, clockB,
clockC, clockC,
clockD, clockD,
displayLayout displayLayout
}; };
} }

View File

@ -35,25 +35,26 @@ Make no assumptions about the order that elements appear in the DOM.
const { test, expect } = require('../../pluginFixtures'); const { test, expect } = require('../../pluginFixtures');
test('Verify that the create button appears and that the Folder Domain Object is available for selection', async ({ page }) => { test('Verify that the create button appears and that the Folder Domain Object is available for selection', async ({
page
}) => {
//Go to baseURL
await page.goto('./', { waitUntil: 'domcontentloaded' });
//Go to baseURL //Click the Create button
await page.goto('./', { waitUntil: 'domcontentloaded' }); await page.click('button:has-text("Create")');
//Click the Create button // Verify that Create Folder appears in the dropdown
await page.click('button:has-text("Create")'); await expect(page.locator(':nth-match(:text("Folder"), 2)')).toBeEnabled();
// Verify that Create Folder appears in the dropdown
await expect(page.locator(':nth-match(:text("Folder"), 2)')).toBeEnabled();
}); });
test('Verify that My Items Tree appears @ipad', async ({ page, openmctConfig }) => { test('Verify that My Items Tree appears @ipad', async ({ page, openmctConfig }) => {
const { myItemsFolderName } = openmctConfig; const { myItemsFolderName } = openmctConfig;
//Test.slow annotation is currently broken. Needs to be fixed in https://github.com/nasa/openmct/issues/5374 //Test.slow annotation is currently broken. Needs to be fixed in https://github.com/nasa/openmct/issues/5374
test.slow(); test.slow();
//Go to baseURL //Go to baseURL
await page.goto('./'); await page.goto('./');
//My Items to be visible //My Items to be visible
await expect(page.locator(`a:has-text("${myItemsFolderName}")`)).toBeEnabled(); await expect(page.locator(`a:has-text("${myItemsFolderName}")`)).toBeEnabled();
}); });

View File

@ -22,151 +22,158 @@
const { test, expect } = require('../../pluginFixtures.js'); const { test, expect } = require('../../pluginFixtures.js');
const { const {
createDomainObjectWithDefaults, createDomainObjectWithDefaults,
openObjectTreeContextMenu openObjectTreeContextMenu
} = require('../../appActions.js'); } = require('../../appActions.js');
test.describe('Main Tree', () => { test.describe('Main Tree', () => {
test.beforeEach(async ({ page }) => { test.beforeEach(async ({ page }) => {
await page.goto('./', { waitUntil: 'domcontentloaded' }); await page.goto('./', { waitUntil: 'domcontentloaded' });
});
test('Creating a child object within a folder and immediately opening it shows the created object in the tree @couchdb', async ({
page
}) => {
test.info().annotations.push({
type: 'issue',
description: 'https://github.com/nasa/openmct/issues/5975'
}); });
test('Creating a child object within a folder and immediately opening it shows the created object in the tree @couchdb', async ({ page }) => { const folder = await createDomainObjectWithDefaults(page, {
test.info().annotations.push({ type: 'Folder'
type: 'issue',
description: 'https://github.com/nasa/openmct/issues/5975'
});
const folder = await createDomainObjectWithDefaults(page, {
type: 'Folder'
});
await page.getByTitle('Show selected item in tree').click();
const clock = await createDomainObjectWithDefaults(page, {
type: 'Clock',
parent: folder.uuid
});
await expandTreePaneItemByName(page, folder.name);
await assertTreeItemIsVisible(page, clock.name);
}); });
test('Creating a child object on one tab and expanding its parent on the other shows the correct composition @2p', async ({ page, openmctConfig }) => { await page.getByTitle('Show selected item in tree').click();
test.info().annotations.push({
type: 'issue',
description: 'https://github.com/nasa/openmct/issues/6391'
});
const { myItemsFolderName } = openmctConfig; const clock = await createDomainObjectWithDefaults(page, {
const page2 = await page.context().newPage(); type: 'Clock',
parent: folder.uuid
// Both pages: Go to baseURL
await Promise.all([
page.goto('./', { waitUntil: 'networkidle' }),
page2.goto('./', { waitUntil: 'networkidle' })
]);
const page1Folder = await createDomainObjectWithDefaults(page, {
type: 'Folder'
});
await expandTreePaneItemByName(page2, myItemsFolderName);
await assertTreeItemIsVisible(page2, page1Folder.name);
}); });
test('Creating a child object on one tab and expanding its parent on the other shows the correct composition @couchdb @2p', async ({ page, openmctConfig }) => { await expandTreePaneItemByName(page, folder.name);
test.info().annotations.push({ await assertTreeItemIsVisible(page, clock.name);
type: 'issue', });
description: 'https://github.com/nasa/openmct/issues/6391'
});
const { myItemsFolderName } = openmctConfig; test('Creating a child object on one tab and expanding its parent on the other shows the correct composition @2p', async ({
const page2 = await page.context().newPage(); page,
openmctConfig
// Both pages: Go to baseURL }) => {
await Promise.all([ test.info().annotations.push({
page.goto('./', { waitUntil: 'networkidle' }), type: 'issue',
page2.goto('./', { waitUntil: 'networkidle' }) description: 'https://github.com/nasa/openmct/issues/6391'
]);
const page1Folder = await createDomainObjectWithDefaults(page, {
type: 'Folder'
});
await expandTreePaneItemByName(page2, myItemsFolderName);
await assertTreeItemIsVisible(page2, page1Folder.name);
}); });
test('Renaming an object reorders the tree @unstable', async ({ page, openmctConfig }) => { const { myItemsFolderName } = openmctConfig;
const { myItemsFolderName } = openmctConfig; const page2 = await page.context().newPage();
await createDomainObjectWithDefaults(page, { // Both pages: Go to baseURL
type: 'Folder', await Promise.all([
name: 'Foo' page.goto('./', { waitUntil: 'networkidle' }),
}); page2.goto('./', { waitUntil: 'networkidle' })
]);
await createDomainObjectWithDefaults(page, { const page1Folder = await createDomainObjectWithDefaults(page, {
type: 'Folder', type: 'Folder'
name: 'Bar'
});
await createDomainObjectWithDefaults(page, {
type: 'Folder',
name: 'Baz'
});
const clock1 = await createDomainObjectWithDefaults(page, {
type: 'Clock',
name: 'aaa'
});
await createDomainObjectWithDefaults(page, {
type: 'Clock',
name: 'www'
});
// Expand the root folder
await expandTreePaneItemByName(page, myItemsFolderName);
await test.step("Reorders objects with the same tree depth", async () => {
await getAndAssertTreeItems(page, ['aaa', 'Bar', 'Baz', 'Foo', 'www']);
await renameObjectFromContextMenu(page, clock1.url, 'zzz');
await getAndAssertTreeItems(page, ['Bar', 'Baz', 'Foo', 'www', 'zzz']);
});
await test.step("Reorders links to objects as well as original objects", async () => {
await page.click('role=treeitem[name=/Bar/]');
await page.dragAndDrop('role=treeitem[name=/www/]', '.c-object-view');
await page.dragAndDrop('role=treeitem[name=/zzz/]', '.c-object-view');
await page.click('role=treeitem[name=/Baz/]');
await page.dragAndDrop('role=treeitem[name=/www/]', '.c-object-view');
await page.dragAndDrop('role=treeitem[name=/zzz/]', '.c-object-view');
await page.click('role=treeitem[name=/Foo/]');
await page.dragAndDrop('role=treeitem[name=/www/]', '.c-object-view');
await page.dragAndDrop('role=treeitem[name=/zzz/]', '.c-object-view');
// Expand the unopened folders
await expandTreePaneItemByName(page, 'Bar');
await expandTreePaneItemByName(page, 'Baz');
await expandTreePaneItemByName(page, 'Foo');
await renameObjectFromContextMenu(page, clock1.url, '___');
await getAndAssertTreeItems(page,
[
"___",
"Bar",
"___",
"www",
"Baz",
"___",
"www",
"Foo",
"___",
"www",
"www"
]);
});
}); });
await expandTreePaneItemByName(page2, myItemsFolderName);
await assertTreeItemIsVisible(page2, page1Folder.name);
});
test('Creating a child object on one tab and expanding its parent on the other shows the correct composition @couchdb @2p', async ({
page,
openmctConfig
}) => {
test.info().annotations.push({
type: 'issue',
description: 'https://github.com/nasa/openmct/issues/6391'
});
const { myItemsFolderName } = openmctConfig;
const page2 = await page.context().newPage();
// Both pages: Go to baseURL
await Promise.all([
page.goto('./', { waitUntil: 'networkidle' }),
page2.goto('./', { waitUntil: 'networkidle' })
]);
const page1Folder = await createDomainObjectWithDefaults(page, {
type: 'Folder'
});
await expandTreePaneItemByName(page2, myItemsFolderName);
await assertTreeItemIsVisible(page2, page1Folder.name);
});
test('Renaming an object reorders the tree @unstable', async ({ page, openmctConfig }) => {
const { myItemsFolderName } = openmctConfig;
await createDomainObjectWithDefaults(page, {
type: 'Folder',
name: 'Foo'
});
await createDomainObjectWithDefaults(page, {
type: 'Folder',
name: 'Bar'
});
await createDomainObjectWithDefaults(page, {
type: 'Folder',
name: 'Baz'
});
const clock1 = await createDomainObjectWithDefaults(page, {
type: 'Clock',
name: 'aaa'
});
await createDomainObjectWithDefaults(page, {
type: 'Clock',
name: 'www'
});
// Expand the root folder
await expandTreePaneItemByName(page, myItemsFolderName);
await test.step('Reorders objects with the same tree depth', async () => {
await getAndAssertTreeItems(page, ['aaa', 'Bar', 'Baz', 'Foo', 'www']);
await renameObjectFromContextMenu(page, clock1.url, 'zzz');
await getAndAssertTreeItems(page, ['Bar', 'Baz', 'Foo', 'www', 'zzz']);
});
await test.step('Reorders links to objects as well as original objects', async () => {
await page.click('role=treeitem[name=/Bar/]');
await page.dragAndDrop('role=treeitem[name=/www/]', '.c-object-view');
await page.dragAndDrop('role=treeitem[name=/zzz/]', '.c-object-view');
await page.click('role=treeitem[name=/Baz/]');
await page.dragAndDrop('role=treeitem[name=/www/]', '.c-object-view');
await page.dragAndDrop('role=treeitem[name=/zzz/]', '.c-object-view');
await page.click('role=treeitem[name=/Foo/]');
await page.dragAndDrop('role=treeitem[name=/www/]', '.c-object-view');
await page.dragAndDrop('role=treeitem[name=/zzz/]', '.c-object-view');
// Expand the unopened folders
await expandTreePaneItemByName(page, 'Bar');
await expandTreePaneItemByName(page, 'Baz');
await expandTreePaneItemByName(page, 'Foo');
await renameObjectFromContextMenu(page, clock1.url, '___');
await getAndAssertTreeItems(page, [
'___',
'Bar',
'___',
'www',
'Baz',
'___',
'www',
'Foo',
'___',
'www',
'www'
]);
});
});
}); });
/** /**
@ -174,22 +181,22 @@ test.describe('Main Tree', () => {
* @param {Array<string>} expected * @param {Array<string>} expected
*/ */
async function getAndAssertTreeItems(page, expected) { async function getAndAssertTreeItems(page, expected) {
const treeItems = page.locator('[role="treeitem"]'); const treeItems = page.locator('[role="treeitem"]');
const allTexts = await treeItems.allInnerTexts(); const allTexts = await treeItems.allInnerTexts();
// Get rid of root folder ('My Items') as its position will not change // Get rid of root folder ('My Items') as its position will not change
allTexts.shift(); allTexts.shift();
expect(allTexts).toEqual(expected); expect(allTexts).toEqual(expected);
} }
async function assertTreeItemIsVisible(page, name) { async function assertTreeItemIsVisible(page, name) {
const mainTree = page.getByRole('tree', { const mainTree = page.getByRole('tree', {
name: 'Main Tree' name: 'Main Tree'
}); });
const treeItem = mainTree.getByRole('treeitem', { const treeItem = mainTree.getByRole('treeitem', {
name name
}); });
await expect(treeItem).toBeVisible(); await expect(treeItem).toBeVisible();
} }
/** /**
@ -197,14 +204,14 @@ async function assertTreeItemIsVisible(page, name) {
* @param {string} name * @param {string} name
*/ */
async function expandTreePaneItemByName(page, name) { async function expandTreePaneItemByName(page, name) {
const mainTree = page.getByRole('tree', { const mainTree = page.getByRole('tree', {
name: 'Main Tree' name: 'Main Tree'
}); });
const treeItem = mainTree.getByRole('treeitem', { const treeItem = mainTree.getByRole('treeitem', {
name, name,
expanded: false expanded: false
}); });
await treeItem.locator('.c-disclosure-triangle').click(); await treeItem.locator('.c-disclosure-triangle').click();
} }
/** /**
@ -214,10 +221,10 @@ async function expandTreePaneItemByName(page, name) {
* @param {string} newName * @param {string} newName
*/ */
async function renameObjectFromContextMenu(page, url, newName) { async function renameObjectFromContextMenu(page, url, newName) {
await openObjectTreeContextMenu(page, url); await openObjectTreeContextMenu(page, url);
await page.click('li:text("Edit Properties")'); await page.click('li:text("Edit Properties")');
const nameInput = page.locator('form[name="mctForm"] .first input[type="text"]'); const nameInput = page.locator('form[name="mctForm"] .first input[type="text"]');
await nameInput.fill(""); await nameInput.fill('');
await nameInput.fill(newName); await nameInput.fill(newName);
await page.click('[aria-label="Save"]'); await page.click('[aria-label="Save"]');
} }

View File

@ -37,141 +37,154 @@ const { test, expect } = require('@playwright/test');
const filePath = 'e2e/test-data/PerformanceDisplayLayout.json'; const filePath = 'e2e/test-data/PerformanceDisplayLayout.json';
test.describe('Performance tests', () => { test.describe('Performance tests', () => {
test.beforeEach(async ({ page, browser }, testInfo) => { test.beforeEach(async ({ page, browser }, testInfo) => {
// Go to baseURL // Go to baseURL
await page.goto('./', { waitUntil: 'networkidle' }); await page.goto('./', { waitUntil: 'networkidle' });
// Click a:has-text("My Items") // Click a:has-text("My Items")
await page.locator('a:has-text("My Items")').click({ await page.locator('a:has-text("My Items")').click({
button: 'right' button: 'right'
});
// Click text=Import from JSON
await page.locator('text=Import from JSON').click();
// Upload Performance Display Layout.json
await page.setInputFiles('#fileElem', filePath);
// Click text=OK
await page.locator('button:has-text("OK")').click();
await expect(page.locator('a:has-text("Performance Display Layout Display Layout")')).toBeVisible();
//Create a Chrome Performance Timeline trace to store as a test artifact
console.log("\n==== Devtools: startTracing ====\n");
await browser.startTracing(page, {
path: `${testInfo.outputPath()}-trace.json`,
screenshots: true
});
}); });
test.afterEach(async ({ page, browser}) => {
console.log("\n==== Devtools: stopTracing ====\n");
await browser.stopTracing();
/* Measurement Section // Click text=Import from JSON
await page.locator('text=Import from JSON').click();
// Upload Performance Display Layout.json
await page.setInputFiles('#fileElem', filePath);
// Click text=OK
await page.locator('button:has-text("OK")').click();
await expect(
page.locator('a:has-text("Performance Display Layout Display Layout")')
).toBeVisible();
//Create a Chrome Performance Timeline trace to store as a test artifact
console.log('\n==== Devtools: startTracing ====\n');
await browser.startTracing(page, {
path: `${testInfo.outputPath()}-trace.json`,
screenshots: true
});
});
test.afterEach(async ({ page, browser }) => {
console.log('\n==== Devtools: stopTracing ====\n');
await browser.stopTracing();
/* Measurement Section
/ The following section includes a block of performance measurements. / The following section includes a block of performance measurements.
*/ */
//Get time difference between viewlarge actionability and evaluate time //Get time difference between viewlarge actionability and evaluate time
await page.evaluate(() => (window.performance.measure("machine-time-difference", "viewlarge.start", "viewLarge.start.test"))); await page.evaluate(() =>
window.performance.measure(
'machine-time-difference',
'viewlarge.start',
'viewLarge.start.test'
)
);
//Get StartTime //Get StartTime
const startTime = await page.evaluate(() => window.performance.timing.navigationStart); const startTime = await page.evaluate(() => window.performance.timing.navigationStart);
console.log('window.performance.timing.navigationStart', startTime); console.log('window.performance.timing.navigationStart', startTime);
//Get All Performance Marks //Get All Performance Marks
const getAllMarksJson = await page.evaluate(() => JSON.stringify(window.performance.getEntriesByType("mark"))); const getAllMarksJson = await page.evaluate(() =>
const getAllMarks = JSON.parse(getAllMarksJson); JSON.stringify(window.performance.getEntriesByType('mark'))
console.log('window.performance.getEntriesByType("mark")', getAllMarks); );
const getAllMarks = JSON.parse(getAllMarksJson);
console.log('window.performance.getEntriesByType("mark")', getAllMarks);
//Get All Performance Measures //Get All Performance Measures
const getAllMeasuresJson = await page.evaluate(() => JSON.stringify(window.performance.getEntriesByType("measure"))); const getAllMeasuresJson = await page.evaluate(() =>
const getAllMeasures = JSON.parse(getAllMeasuresJson); JSON.stringify(window.performance.getEntriesByType('measure'))
console.log('window.performance.getEntriesByType("measure")', getAllMeasures); );
const getAllMeasures = JSON.parse(getAllMeasuresJson);
}); console.log('window.performance.getEntriesByType("measure")', getAllMeasures);
/* The following test will navigate to a previously created Performance Display Layout and measure the });
/* The following test will navigate to a previously created Performance Display Layout and measure the
/ following metrics: / following metrics:
/ - ElementResourceTiming / - ElementResourceTiming
/ - Interaction Timing / - Interaction Timing
*/ */
test('Embedded View Large for Imagery is performant in Fixed Time', async ({ page, browser }) => { test('Embedded View Large for Imagery is performant in Fixed Time', async ({ page, browser }) => {
const client = await page.context().newCDPSession(page); const client = await page.context().newCDPSession(page);
// Tell the DevTools session to record performance metrics // Tell the DevTools session to record performance metrics
// https://chromedevtools.github.io/devtools-protocol/tot/Performance/#method-getMetrics // https://chromedevtools.github.io/devtools-protocol/tot/Performance/#method-getMetrics
await client.send('Performance.enable'); await client.send('Performance.enable');
// Go to baseURL // Go to baseURL
await page.goto('./'); await page.goto('./');
// Search Available after Launch // Search Available after Launch
await page.locator('[aria-label="OpenMCT Search"] input[type="search"]').click(); await page.locator('[aria-label="OpenMCT Search"] input[type="search"]').click();
await page.evaluate(() => window.performance.mark("search-available")); await page.evaluate(() => window.performance.mark('search-available'));
// Fill Search input // Fill Search input
await page.locator('[aria-label="OpenMCT Search"] input[type="search"]').fill('Performance Display Layout'); await page
await page.evaluate(() => window.performance.mark("search-entered")); .locator('[aria-label="OpenMCT Search"] input[type="search"]')
//Search Result Appears and is clicked .fill('Performance Display Layout');
await Promise.all([ await page.evaluate(() => window.performance.mark('search-entered'));
page.waitForNavigation(), //Search Result Appears and is clicked
page.locator('a:has-text("Performance Display Layout")').first().click(), await Promise.all([
page.evaluate(() => window.performance.mark("click-search-result")) page.waitForNavigation(),
]); page.locator('a:has-text("Performance Display Layout")').first().click(),
page.evaluate(() => window.performance.mark('click-search-result'))
]);
//Time to Example Imagery Frame loads within Display Layout //Time to Example Imagery Frame loads within Display Layout
await page.waitForSelector('.c-imagery__main-image__bg', { state: 'visible'}); await page.waitForSelector('.c-imagery__main-image__bg', { state: 'visible' });
//Time to Example Imagery object loads //Time to Example Imagery object loads
await page.waitForSelector('.c-imagery__main-image__background-image', { state: 'visible'}); await page.waitForSelector('.c-imagery__main-image__background-image', { state: 'visible' });
//Get background-image url from background-image css prop
const backgroundImage = await page.locator('.c-imagery__main-image__background-image');
let backgroundImageUrl = await backgroundImage.evaluate((el) => {
return window.getComputedStyle(el).getPropertyValue('background-image').match(/url\(([^)]+)\)/)[1];
});
backgroundImageUrl = backgroundImageUrl.slice(1, -1); //forgive me, padre
console.log('backgroundImageurl ' + backgroundImageUrl);
//Get ResourceTiming of background-image jpg
const resourceTimingJson = await page.evaluate((bgImageUrl) =>
JSON.stringify(window.performance.getEntriesByName(bgImageUrl).pop()),
backgroundImageUrl
);
console.log('resourceTimingJson ' + resourceTimingJson);
//Open Large view
await page.locator('button:has-text("Large View")').click(); //This action includes the performance.mark named 'viewLarge.start'
await page.evaluate(() => window.performance.mark("viewLarge.start.test")); //This is a mark only to compare evaluate timing
//Time to Imagery Rendered in Large Frame
await page.waitForSelector('.c-imagery__main-image__bg', { state: 'visible'});
await page.evaluate(() => window.performance.mark("background-image-frame"));
//Time to Example Imagery object loads
await page.waitForSelector('.c-imagery__main-image__background-image', { state: 'visible'});
await page.evaluate(() => window.performance.mark("background-image-visible"));
// Get Current number of images in thumbstrip
await page.waitForSelector('.c-imagery__thumb');
const thumbCount = await page.locator('.c-imagery__thumb').count();
console.log('number of thumbs rendered ' + thumbCount);
await page.locator('.c-imagery__thumb').last().click();
//Get ResourceTiming of all jpg resources
const resourceTimingJson2 = await page.evaluate(() =>
JSON.stringify(window.performance.getEntriesByType('resource'))
);
const resourceTiming = JSON.parse(resourceTimingJson2);
const jpgResourceTiming = resourceTiming.find((element) =>
element.name.includes('.jpg')
);
console.log('jpgResourceTiming ' + JSON.stringify(jpgResourceTiming));
// Click Close Icon
await page.locator('[aria-label="Close"]').click();
await page.evaluate(() => window.performance.mark("view-large-close-button"));
//await client.send('HeapProfiler.enable');
await client.send('HeapProfiler.collectGarbage');
let performanceMetrics = await client.send('Performance.getMetrics');
console.log(performanceMetrics.metrics);
//Get background-image url from background-image css prop
const backgroundImage = await page.locator('.c-imagery__main-image__background-image');
let backgroundImageUrl = await backgroundImage.evaluate((el) => {
return window
.getComputedStyle(el)
.getPropertyValue('background-image')
.match(/url\(([^)]+)\)/)[1];
}); });
backgroundImageUrl = backgroundImageUrl.slice(1, -1); //forgive me, padre
console.log('backgroundImageurl ' + backgroundImageUrl);
//Get ResourceTiming of background-image jpg
const resourceTimingJson = await page.evaluate(
(bgImageUrl) => JSON.stringify(window.performance.getEntriesByName(bgImageUrl).pop()),
backgroundImageUrl
);
console.log('resourceTimingJson ' + resourceTimingJson);
//Open Large view
await page.locator('button:has-text("Large View")').click(); //This action includes the performance.mark named 'viewLarge.start'
await page.evaluate(() => window.performance.mark('viewLarge.start.test')); //This is a mark only to compare evaluate timing
//Time to Imagery Rendered in Large Frame
await page.waitForSelector('.c-imagery__main-image__bg', { state: 'visible' });
await page.evaluate(() => window.performance.mark('background-image-frame'));
//Time to Example Imagery object loads
await page.waitForSelector('.c-imagery__main-image__background-image', { state: 'visible' });
await page.evaluate(() => window.performance.mark('background-image-visible'));
// Get Current number of images in thumbstrip
await page.waitForSelector('.c-imagery__thumb');
const thumbCount = await page.locator('.c-imagery__thumb').count();
console.log('number of thumbs rendered ' + thumbCount);
await page.locator('.c-imagery__thumb').last().click();
//Get ResourceTiming of all jpg resources
const resourceTimingJson2 = await page.evaluate(() =>
JSON.stringify(window.performance.getEntriesByType('resource'))
);
const resourceTiming = JSON.parse(resourceTimingJson2);
const jpgResourceTiming = resourceTiming.find((element) => element.name.includes('.jpg'));
console.log('jpgResourceTiming ' + JSON.stringify(jpgResourceTiming));
// Click Close Icon
await page.locator('[aria-label="Close"]').click();
await page.evaluate(() => window.performance.mark('view-large-close-button'));
//await client.send('HeapProfiler.enable');
await client.send('HeapProfiler.collectGarbage');
let performanceMetrics = await client.send('Performance.getMetrics');
console.log(performanceMetrics.metrics);
});
}); });

View File

@ -38,82 +38,84 @@ const filePath = 'e2e/test-data/PerformanceDisplayLayout.json';
// eslint-disable-next-line playwright/no-skipped-test // eslint-disable-next-line playwright/no-skipped-test
test.describe.skip('Memory Performance tests', () => { test.describe.skip('Memory Performance tests', () => {
test.beforeEach(async ({ page, browser }, testInfo) => { test.beforeEach(async ({ page, browser }, testInfo) => {
// Go to baseURL // Go to baseURL
await page.goto('./', { waitUntil: 'networkidle' }); await page.goto('./', { waitUntil: 'networkidle' });
// Click a:has-text("My Items") // Click a:has-text("My Items")
await page.locator('a:has-text("My Items")').click({ await page.locator('a:has-text("My Items")').click({
button: 'right' button: 'right'
});
// Click text=Import from JSON
await page.locator('text=Import from JSON').click();
// Upload Performance Display Layout.json
await page.setInputFiles('#fileElem', filePath);
// Click text=OK
await page.locator('text=OK').click();
await expect(page.locator('a:has-text("Performance Display Layout Display Layout")')).toBeVisible();
}); });
test('Embedded View Large for Imagery is performant in Fixed Time', async ({ page, browser }) => { // Click text=Import from JSON
await page.locator('text=Import from JSON').click();
await page.goto('./', {waitUntil: 'networkidle'}); // Upload Performance Display Layout.json
await page.setInputFiles('#fileElem', filePath);
// To to Search Available after Launch // Click text=OK
await page.locator('[aria-label="OpenMCT Search"] input[type="search"]').click(); await page.locator('text=OK').click();
// Fill Search input
await page.locator('[aria-label="OpenMCT Search"] input[type="search"]').fill('Performance Display Layout');
//Search Result Appears and is clicked
await Promise.all([
page.waitForNavigation(),
page.locator('a:has-text("Performance Display Layout")').first().click()
]);
//Time to Example Imagery Frame loads within Display Layout await expect(
await page.waitForSelector('.c-imagery__main-image__bg', { state: 'visible'}); page.locator('a:has-text("Performance Display Layout Display Layout")')
//Time to Example Imagery object loads ).toBeVisible();
await page.waitForSelector('.c-imagery__main-image__background-image', { state: 'visible'}); });
const client = await page.context().newCDPSession(page); test('Embedded View Large for Imagery is performant in Fixed Time', async ({ page, browser }) => {
await client.send('HeapProfiler.enable'); await page.goto('./', { waitUntil: 'networkidle' });
await client.send('HeapProfiler.startSampling');
// await client.send('HeapProfiler.collectGarbage');
await client.send('Performance.enable');
let performanceMetricsBefore = await client.send('Performance.getMetrics'); // To to Search Available after Launch
console.log(performanceMetricsBefore.metrics); await page.locator('[aria-label="OpenMCT Search"] input[type="search"]').click();
// Fill Search input
await page
.locator('[aria-label="OpenMCT Search"] input[type="search"]')
.fill('Performance Display Layout');
//Search Result Appears and is clicked
await Promise.all([
page.waitForNavigation(),
page.locator('a:has-text("Performance Display Layout")').first().click()
]);
//await client.send('Performance.disable'); //Time to Example Imagery Frame loads within Display Layout
await page.waitForSelector('.c-imagery__main-image__bg', { state: 'visible' });
//Time to Example Imagery object loads
await page.waitForSelector('.c-imagery__main-image__background-image', { state: 'visible' });
//Open Large view const client = await page.context().newCDPSession(page);
await page.locator('button:has-text("Large View")').click(); await client.send('HeapProfiler.enable');
await client.send('HeapProfiler.takeHeapSnapshot'); await client.send('HeapProfiler.startSampling');
// await client.send('HeapProfiler.collectGarbage');
await client.send('Performance.enable');
//Time to Imagery Rendered in Large Frame let performanceMetricsBefore = await client.send('Performance.getMetrics');
await page.waitForSelector('.c-imagery__main-image__bg', { state: 'visible'}); console.log(performanceMetricsBefore.metrics);
//Time to Example Imagery object loads //await client.send('Performance.disable');
await page.waitForSelector('.c-imagery__main-image__background-image', { state: 'visible'});
// Click Close Icon //Open Large view
await page.locator('.c-click-icon').click(); await page.locator('button:has-text("Large View")').click();
await client.send('HeapProfiler.takeHeapSnapshot');
//Time to Example Imagery Frame loads within Display Layout //Time to Imagery Rendered in Large Frame
await page.waitForSelector('.c-imagery__main-image__bg', { state: 'visible'}); await page.waitForSelector('.c-imagery__main-image__bg', { state: 'visible' });
//Time to Example Imagery object loads
await page.waitForSelector('.c-imagery__main-image__background-image', { state: 'visible'});
await client.send('HeapProfiler.collectGarbage'); //Time to Example Imagery object loads
//await client.send('Performance.enable'); await page.waitForSelector('.c-imagery__main-image__background-image', { state: 'visible' });
let performanceMetricsAfter = await client.send('Performance.getMetrics'); // Click Close Icon
console.log(performanceMetricsAfter.metrics); await page.locator('.c-click-icon').click();
//await client.send('Performance.disable'); //Time to Example Imagery Frame loads within Display Layout
await page.waitForSelector('.c-imagery__main-image__bg', { state: 'visible' });
//Time to Example Imagery object loads
await page.waitForSelector('.c-imagery__main-image__background-image', { state: 'visible' });
}); await client.send('HeapProfiler.collectGarbage');
//await client.send('Performance.enable');
let performanceMetricsAfter = await client.send('Performance.getMetrics');
console.log(performanceMetricsAfter.metrics);
//await client.send('Performance.disable');
});
}); });

View File

@ -36,124 +36,131 @@ const { test, expect } = require('@playwright/test');
const notebookFilePath = 'e2e/test-data/PerformanceNotebook.json'; const notebookFilePath = 'e2e/test-data/PerformanceNotebook.json';
test.describe('Performance tests', () => { test.describe('Performance tests', () => {
test.beforeEach(async ({ page, browser }, testInfo) => { test.beforeEach(async ({ page, browser }, testInfo) => {
// Go to baseURL // Go to baseURL
await page.goto('./', { waitUntil: 'networkidle' }); await page.goto('./', { waitUntil: 'networkidle' });
// Click a:has-text("My Items") // Click a:has-text("My Items")
await page.locator('a:has-text("My Items")').click({ await page.locator('a:has-text("My Items")').click({
button: 'right' button: 'right'
});
// Click text=Import from JSON
await page.locator('text=Import from JSON').click();
// Upload Performance Display Layout.json
await page.setInputFiles('#fileElem', notebookFilePath);
// TODO Fix this
await page.locator('text=OK >> nth=1').click();
await expect(page.locator('a:has-text("Performance Notebook")')).toBeVisible();
//Create a Chrome Performance Timeline trace to store as a test artifact
console.log("\n==== Devtools: startTracing ====\n");
await browser.startTracing(page, {
path: `${testInfo.outputPath()}-trace.json`,
screenshots: true
});
}); });
test.afterEach(async ({ page, browser}) => {
console.log("\n==== Devtools: stopTracing ====\n");
await browser.stopTracing();
/* Measurement Section // Click text=Import from JSON
await page.locator('text=Import from JSON').click();
// Upload Performance Display Layout.json
await page.setInputFiles('#fileElem', notebookFilePath);
// TODO Fix this
await page.locator('text=OK >> nth=1').click();
await expect(page.locator('a:has-text("Performance Notebook")')).toBeVisible();
//Create a Chrome Performance Timeline trace to store as a test artifact
console.log('\n==== Devtools: startTracing ====\n');
await browser.startTracing(page, {
path: `${testInfo.outputPath()}-trace.json`,
screenshots: true
});
});
test.afterEach(async ({ page, browser }) => {
console.log('\n==== Devtools: stopTracing ====\n');
await browser.stopTracing();
/* Measurement Section
/ The following section includes a block of performance measurements. / The following section includes a block of performance measurements.
*/ */
const startTime = await page.evaluate(() => window.performance.timing.navigationStart); const startTime = await page.evaluate(() => window.performance.timing.navigationStart);
console.log('window.performance.timing.navigationStart', startTime); console.log('window.performance.timing.navigationStart', startTime);
//Get All Performance Marks //Get All Performance Marks
const getAllMarksJson = await page.evaluate(() => JSON.stringify(window.performance.getEntriesByType("mark"))); const getAllMarksJson = await page.evaluate(() =>
const getAllMarks = JSON.parse(getAllMarksJson); JSON.stringify(window.performance.getEntriesByType('mark'))
console.log('window.performance.getEntriesByType("mark")', getAllMarks); );
const getAllMarks = JSON.parse(getAllMarksJson);
console.log('window.performance.getEntriesByType("mark")', getAllMarks);
//Get All Performance Measures //Get All Performance Measures
const getAllMeasuresJson = await page.evaluate(() => JSON.stringify(window.performance.getEntriesByType("measure"))); const getAllMeasuresJson = await page.evaluate(() =>
const getAllMeasures = JSON.parse(getAllMeasuresJson); JSON.stringify(window.performance.getEntriesByType('measure'))
console.log('window.performance.getEntriesByType("measure")', getAllMeasures); );
const getAllMeasures = JSON.parse(getAllMeasuresJson);
}); console.log('window.performance.getEntriesByType("measure")', getAllMeasures);
/* The following test will navigate to a previously created Performance Display Layout and measure the });
/* The following test will navigate to a previously created Performance Display Layout and measure the
/ following metrics: / following metrics:
/ - ElementResourceTiming / - ElementResourceTiming
/ - Interaction Timing / - Interaction Timing
*/ */
test('Notebook Search, Add Entry, Update Entry are performant', async ({ page, browser }) => { test('Notebook Search, Add Entry, Update Entry are performant', async ({ page, browser }) => {
const client = await page.context().newCDPSession(page); const client = await page.context().newCDPSession(page);
// Tell the DevTools session to record performance metrics // Tell the DevTools session to record performance metrics
// https://chromedevtools.github.io/devtools-protocol/tot/Performance/#method-getMetrics // https://chromedevtools.github.io/devtools-protocol/tot/Performance/#method-getMetrics
await client.send('Performance.enable'); await client.send('Performance.enable');
// Go to baseURL // Go to baseURL
await page.goto('./'); await page.goto('./');
// To to Search Available after Launch // To to Search Available after Launch
await page.locator('[aria-label="OpenMCT Search"] input[type="search"]').click(); await page.locator('[aria-label="OpenMCT Search"] input[type="search"]').click();
await page.evaluate(() => window.performance.mark("search-available")); await page.evaluate(() => window.performance.mark('search-available'));
// Fill Search input // Fill Search input
await page.locator('[aria-label="OpenMCT Search"] input[type="search"]').fill('Performance Notebook'); await page
await page.evaluate(() => window.performance.mark("search-entered")); .locator('[aria-label="OpenMCT Search"] input[type="search"]')
//Search Result Appears and is clicked .fill('Performance Notebook');
await Promise.all([ await page.evaluate(() => window.performance.mark('search-entered'));
page.waitForNavigation(), //Search Result Appears and is clicked
page.locator('a:has-text("Performance Notebook")').first().click(), await Promise.all([
page.evaluate(() => window.performance.mark("click-search-result")) page.waitForNavigation(),
]); page.locator('a:has-text("Performance Notebook")').first().click(),
page.evaluate(() => window.performance.mark('click-search-result'))
]);
await page.waitForSelector('.c-tree__item c-tree-and-search__loading loading', {state: 'hidden'}); await page.waitForSelector('.c-tree__item c-tree-and-search__loading loading', {
await page.evaluate(() => window.performance.mark("search-spinner-gone")); state: 'hidden'
await page.waitForSelector('.l-browse-bar__object-name', { state: 'visible'});
await page.evaluate(() => window.performance.mark("object-title-appears"));
await page.waitForSelector('.c-notebook__entry >> nth=0', { state: 'visible'});
await page.evaluate(() => window.performance.mark("notebook-entry-appears"));
// Click Add new Notebook Entry
await page.locator('.c-notebook__drag-area').click();
await page.evaluate(() => window.performance.mark("new-notebook-entry-created"));
// Enter Notebook Entry text
await page.locator('div.c-ne__text').last().fill('New Entry');
await page.keyboard.press('Enter');
await page.evaluate(() => window.performance.mark("new-notebook-entry-filled"));
//Individual Notebook Entry Search
await page.evaluate(() => window.performance.mark("notebook-search-start"));
await page.locator('.c-notebook__search >> input').fill('Existing Entry');
await page.evaluate(() => window.performance.mark("notebook-search-filled"));
await page.waitForSelector('text=Search Results (3)', { state: 'visible'});
await page.evaluate(() => window.performance.mark("notebook-search-processed"));
await page.waitForSelector('.c-notebook__entry >> nth=2', { state: 'visible'});
await page.evaluate(() => window.performance.mark("notebook-search-processed"));
//Clear Search
await page.locator('.c-search.c-notebook__search .c-search__input').hover();
await page.locator('.c-search.c-notebook__search .c-search__clear-input').click();
await page.evaluate(() => window.performance.mark("notebook-search-processed"));
// Hover on Last
await page.evaluate(() => window.performance.mark("new-notebook-entry-delete"));
await page.locator('div.c-ne__time-and-content').last().hover();
await page.locator('button[title="Delete this entry"]').last().click();
await page.locator('button:has-text("Ok")').click();
await page.waitForSelector('.c-notebook__entry >> nth=3', { state: 'detached'});
await page.evaluate(() => window.performance.mark("new-notebook-entry-deleted"));
//await client.send('HeapProfiler.enable');
await client.send('HeapProfiler.collectGarbage');
let performanceMetrics = await client.send('Performance.getMetrics');
console.log(performanceMetrics.metrics);
}); });
await page.evaluate(() => window.performance.mark('search-spinner-gone'));
await page.waitForSelector('.l-browse-bar__object-name', { state: 'visible' });
await page.evaluate(() => window.performance.mark('object-title-appears'));
await page.waitForSelector('.c-notebook__entry >> nth=0', { state: 'visible' });
await page.evaluate(() => window.performance.mark('notebook-entry-appears'));
// Click Add new Notebook Entry
await page.locator('.c-notebook__drag-area').click();
await page.evaluate(() => window.performance.mark('new-notebook-entry-created'));
// Enter Notebook Entry text
await page.locator('div.c-ne__text').last().fill('New Entry');
await page.keyboard.press('Enter');
await page.evaluate(() => window.performance.mark('new-notebook-entry-filled'));
//Individual Notebook Entry Search
await page.evaluate(() => window.performance.mark('notebook-search-start'));
await page.locator('.c-notebook__search >> input').fill('Existing Entry');
await page.evaluate(() => window.performance.mark('notebook-search-filled'));
await page.waitForSelector('text=Search Results (3)', { state: 'visible' });
await page.evaluate(() => window.performance.mark('notebook-search-processed'));
await page.waitForSelector('.c-notebook__entry >> nth=2', { state: 'visible' });
await page.evaluate(() => window.performance.mark('notebook-search-processed'));
//Clear Search
await page.locator('.c-search.c-notebook__search .c-search__input').hover();
await page.locator('.c-search.c-notebook__search .c-search__clear-input').click();
await page.evaluate(() => window.performance.mark('notebook-search-processed'));
// Hover on Last
await page.evaluate(() => window.performance.mark('new-notebook-entry-delete'));
await page.locator('div.c-ne__time-and-content').last().hover();
await page.locator('button[title="Delete this entry"]').last().click();
await page.locator('button:has-text("Ok")').click();
await page.waitForSelector('.c-notebook__entry >> nth=3', { state: 'detached' });
await page.evaluate(() => window.performance.mark('new-notebook-entry-deleted'));
//await client.send('HeapProfiler.enable');
await client.send('HeapProfiler.collectGarbage');
let performanceMetrics = await client.send('Performance.getMetrics');
console.log(performanceMetrics.metrics);
});
}); });

View File

@ -40,22 +40,23 @@ const path = require('path');
const CUSTOM_NAME = 'CUSTOM_NAME'; const CUSTOM_NAME = 'CUSTOM_NAME';
test.describe('Visual - addInit', () => { test.describe('Visual - addInit', () => {
test.use({ test.use({
clockOptions: { clockOptions: {
now: 0, //Set browser clock to UNIX Epoch now: 0, //Set browser clock to UNIX Epoch
shouldAdvanceTime: false //Don't advance the clock shouldAdvanceTime: false //Don't advance the clock
} }
});
test('Restricted Notebook is visually correct @addInit @unstable', async ({ page, theme }) => {
await page.addInitScript({
path: path.join(__dirname, '../../helper', './addInitRestrictedNotebook.js')
}); });
//Go to baseURL
await page.goto('./#/browse/mine?hideTree=true', { waitUntil: 'networkidle' });
test('Restricted Notebook is visually correct @addInit @unstable', async ({ page, theme }) => { await createDomainObjectWithDefaults(page, { type: CUSTOM_NAME });
await page.addInitScript({ path: path.join(__dirname, '../../helper', './addInitRestrictedNotebook.js') });
//Go to baseURL
await page.goto('./#/browse/mine?hideTree=true', { waitUntil: 'networkidle' });
await createDomainObjectWithDefaults(page, { type: CUSTOM_NAME }); // Take a snapshot of the newly created CUSTOM_NAME notebook
await percySnapshot(page, `Restricted Notebook with CUSTOM_NAME (theme: '${theme}')`);
// Take a snapshot of the newly created CUSTOM_NAME notebook });
await percySnapshot(page, `Restricted Notebook with CUSTOM_NAME (theme: '${theme}')`);
});
}); });

View File

@ -26,67 +26,67 @@ const { createDomainObjectWithDefaults } = require('../../../appActions.js');
const percySnapshot = require('@percy/playwright'); const percySnapshot = require('@percy/playwright');
test.describe('Visual - Tree Pane', () => { test.describe('Visual - Tree Pane', () => {
test('Tree pane in various states @unstable', async ({ page, theme, openmctConfig }) => { test('Tree pane in various states @unstable', async ({ page, theme, openmctConfig }) => {
const { myItemsFolderName } = openmctConfig; const { myItemsFolderName } = openmctConfig;
await page.goto('./#/browse/mine', { waitUntil: 'networkidle' }); await page.goto('./#/browse/mine', { waitUntil: 'networkidle' });
const foo = await createDomainObjectWithDefaults(page, { const foo = await createDomainObjectWithDefaults(page, {
type: 'Folder', type: 'Folder',
name: "Foo Folder" name: 'Foo Folder'
});
const bar = await createDomainObjectWithDefaults(page, {
type: 'Folder',
name: "Bar Folder",
parent: foo.uuid
});
const baz = await createDomainObjectWithDefaults(page, {
type: 'Folder',
name: "Baz Folder",
parent: bar.uuid
});
await createDomainObjectWithDefaults(page, {
type: 'Clock',
name: 'A Clock'
});
await createDomainObjectWithDefaults(page, {
type: 'Clock',
name: 'Z Clock'
});
const treePane = "[role=tree][aria-label='Main Tree']";
await percySnapshot(page, `Tree Pane w/ collapsed tree (theme: ${theme})`, {
scope: treePane
});
await expandTreePaneItemByName(page, myItemsFolderName);
await page.goto(foo.url);
await page.dragAndDrop('role=treeitem[name=/A Clock/]', '.c-object-view');
await page.dragAndDrop('role=treeitem[name=/Z Clock/]', '.c-object-view');
await page.goto(bar.url);
await page.dragAndDrop('role=treeitem[name=/A Clock/]', '.c-object-view');
await page.dragAndDrop('role=treeitem[name=/Z Clock/]', '.c-object-view');
await page.goto(baz.url);
await page.dragAndDrop('role=treeitem[name=/A Clock/]', '.c-object-view');
await page.dragAndDrop('role=treeitem[name=/Z Clock/]', '.c-object-view');
await percySnapshot(page, `Tree Pane w/ single level expanded (theme: ${theme})`, {
scope: treePane
});
await expandTreePaneItemByName(page, foo.name);
await expandTreePaneItemByName(page, bar.name);
await expandTreePaneItemByName(page, baz.name);
await percySnapshot(page, `Tree Pane w/ multiple levels expanded (theme: ${theme})`, {
scope: treePane
});
}); });
const bar = await createDomainObjectWithDefaults(page, {
type: 'Folder',
name: 'Bar Folder',
parent: foo.uuid
});
const baz = await createDomainObjectWithDefaults(page, {
type: 'Folder',
name: 'Baz Folder',
parent: bar.uuid
});
await createDomainObjectWithDefaults(page, {
type: 'Clock',
name: 'A Clock'
});
await createDomainObjectWithDefaults(page, {
type: 'Clock',
name: 'Z Clock'
});
const treePane = "[role=tree][aria-label='Main Tree']";
await percySnapshot(page, `Tree Pane w/ collapsed tree (theme: ${theme})`, {
scope: treePane
});
await expandTreePaneItemByName(page, myItemsFolderName);
await page.goto(foo.url);
await page.dragAndDrop('role=treeitem[name=/A Clock/]', '.c-object-view');
await page.dragAndDrop('role=treeitem[name=/Z Clock/]', '.c-object-view');
await page.goto(bar.url);
await page.dragAndDrop('role=treeitem[name=/A Clock/]', '.c-object-view');
await page.dragAndDrop('role=treeitem[name=/Z Clock/]', '.c-object-view');
await page.goto(baz.url);
await page.dragAndDrop('role=treeitem[name=/A Clock/]', '.c-object-view');
await page.dragAndDrop('role=treeitem[name=/Z Clock/]', '.c-object-view');
await percySnapshot(page, `Tree Pane w/ single level expanded (theme: ${theme})`, {
scope: treePane
});
await expandTreePaneItemByName(page, foo.name);
await expandTreePaneItemByName(page, bar.name);
await expandTreePaneItemByName(page, baz.name);
await percySnapshot(page, `Tree Pane w/ multiple levels expanded (theme: ${theme})`, {
scope: treePane
});
});
}); });
/** /**
@ -94,8 +94,8 @@ test.describe('Visual - Tree Pane', () => {
* @param {string} name * @param {string} name
*/ */
async function expandTreePaneItemByName(page, name) { async function expandTreePaneItemByName(page, name) {
const treePane = page.getByTestId('tree-pane'); const treePane = page.getByTestId('tree-pane');
const treeItem = treePane.locator(`role=treeitem[expanded=false][name=/${name}/]`); const treeItem = treePane.locator(`role=treeitem[expanded=false][name=/${name}/]`);
const expandTriangle = treeItem.locator('.c-disclosure-triangle'); const expandTriangle = treeItem.locator('.c-disclosure-triangle');
await expandTriangle.click(); await expandTriangle.click();
} }

View File

@ -31,26 +31,26 @@ const { test, expect } = require('../../pluginFixtures');
const percySnapshot = require('@percy/playwright'); const percySnapshot = require('@percy/playwright');
test.describe('Visual - Controlled Clock @localStorage', () => { test.describe('Visual - Controlled Clock @localStorage', () => {
test.use({ test.use({
storageState: './e2e/test-data/VisualTestData_storage.json', storageState: './e2e/test-data/VisualTestData_storage.json',
clockOptions: { clockOptions: {
now: 0, //Set browser clock to UNIX Epoch now: 0, //Set browser clock to UNIX Epoch
shouldAdvanceTime: false //Don't advance the clock shouldAdvanceTime: false //Don't advance the clock
} }
}); });
test('Overlay Plot Loading Indicator @localStorage', async ({ page, theme }) => { test('Overlay Plot Loading Indicator @localStorage', async ({ page, theme }) => {
// Go to baseURL // Go to baseURL
await page.goto('./#/browse/mine?hideTree=true', { waitUntil: 'networkidle' }); await page.goto('./#/browse/mine?hideTree=true', { waitUntil: 'networkidle' });
await page.locator('a:has-text("Unnamed Overlay Plot Overlay Plot")').click(); await page.locator('a:has-text("Unnamed Overlay Plot Overlay Plot")').click();
//Ensure that we're on the Unnamed Overlay Plot object //Ensure that we're on the Unnamed Overlay Plot object
await expect(page.locator('.l-browse-bar__object-name')).toContainText('Unnamed Overlay Plot'); await expect(page.locator('.l-browse-bar__object-name')).toContainText('Unnamed Overlay Plot');
//Wait for canvas to be rendered and stop animating //Wait for canvas to be rendered and stop animating
await page.locator('canvas >> nth=1').hover({trial: true}); await page.locator('canvas >> nth=1').hover({ trial: true });
//Take snapshot of Sine Wave Generator within Overlay Plot //Take snapshot of Sine Wave Generator within Overlay Plot
await percySnapshot(page, `SineWaveInOverlayPlot (theme: '${theme}')`); await percySnapshot(page, `SineWaveInOverlayPlot (theme: '${theme}')`);
}); });
}); });

View File

@ -37,132 +37,134 @@ const percySnapshot = require('@percy/playwright');
const { createDomainObjectWithDefaults } = require('../../appActions'); const { createDomainObjectWithDefaults } = require('../../appActions');
test.describe('Visual - Default', () => { test.describe('Visual - Default', () => {
test.beforeEach(async ({ page }) => { test.beforeEach(async ({ page }) => {
//Go to baseURL and Hide Tree //Go to baseURL and Hide Tree
await page.goto('./#/browse/mine?hideTree=true', { waitUntil: 'networkidle' }); await page.goto('./#/browse/mine?hideTree=true', { waitUntil: 'networkidle' });
}); });
test.use({ test.use({
clockOptions: { clockOptions: {
now: 0, //Set browser clock to UNIX Epoch now: 0, //Set browser clock to UNIX Epoch
shouldAdvanceTime: false //Don't advance the clock shouldAdvanceTime: false //Don't advance the clock
} }
});
test('Visual - Root and About', async ({ page, theme }) => {
// Verify that Create button is actionable
await expect(page.locator('button:has-text("Create")')).toBeEnabled();
// Take a snapshot of the Dashboard
await percySnapshot(page, `Root (theme: '${theme}')`);
// Click About button
await page.click('.l-shell__app-logo');
// Modify the Build information in 'about' to be consistent run-over-run
const versionInformationLocator = page.locator('ul.t-info.l-info.s-info').first();
await expect(versionInformationLocator).toBeEnabled();
await versionInformationLocator.evaluate(
(node) =>
(node.innerHTML =
'<li>Version: visual-snapshot</li> <li>Build Date: Mon Nov 15 2021 08:07:51 GMT-0800 (Pacific Standard Time)</li> <li>Revision: 93049cdbc6c047697ca204893db9603b864b8c9f</li> <li>Branch: master</li>')
);
// Take a snapshot of the About modal
await percySnapshot(page, `About (theme: '${theme}')`);
});
test('Visual - Default Condition Set @unstable', async ({ page, theme }) => {
await createDomainObjectWithDefaults(page, { type: 'Condition Set' });
// Take a snapshot of the newly created Condition Set object
await percySnapshot(page, `Default Condition Set (theme: '${theme}')`);
});
test('Visual - Default Condition Widget @unstable', async ({ page, theme }) => {
test.info().annotations.push({
type: 'issue',
description: 'https://github.com/nasa/openmct/issues/5349'
}); });
test('Visual - Root and About', async ({ page, theme }) => { await createDomainObjectWithDefaults(page, { type: 'Condition Widget' });
// Verify that Create button is actionable
await expect(page.locator('button:has-text("Create")')).toBeEnabled();
// Take a snapshot of the Dashboard // Take a snapshot of the newly created Condition Widget object
await percySnapshot(page, `Root (theme: '${theme}')`); await percySnapshot(page, `Default Condition Widget (theme: '${theme}')`);
});
// Click About button test('Visual - Time Conductor start time is less than end time', async ({ page, theme }) => {
await page.click('.l-shell__app-logo'); const year = new Date().getFullYear();
// Modify the Build information in 'about' to be consistent run-over-run let startDate = 'xxxx-01-01 01:00:00.000Z';
const versionInformationLocator = page.locator('ul.t-info.l-info.s-info').first(); startDate = year + startDate.substring(4);
await expect(versionInformationLocator).toBeEnabled();
await versionInformationLocator.evaluate(node => node.innerHTML = '<li>Version: visual-snapshot</li> <li>Build Date: Mon Nov 15 2021 08:07:51 GMT-0800 (Pacific Standard Time)</li> <li>Revision: 93049cdbc6c047697ca204893db9603b864b8c9f</li> <li>Branch: master</li>');
// Take a snapshot of the About modal let endDate = 'xxxx-01-01 02:00:00.000Z';
await percySnapshot(page, `About (theme: '${theme}')`); endDate = year + endDate.substring(4);
});
test('Visual - Default Condition Set @unstable', async ({ page, theme }) => { await page.locator('input[type="text"]').nth(1).fill(endDate.toString());
await page.locator('input[type="text"]').first().fill(startDate.toString());
await createDomainObjectWithDefaults(page, { type: 'Condition Set' }); // verify no error msg
await percySnapshot(page, `Default Time conductor (theme: '${theme}')`);
// Take a snapshot of the newly created Condition Set object startDate = year + 1 + startDate.substring(4);
await percySnapshot(page, `Default Condition Set (theme: '${theme}')`); await page.locator('input[type="text"]').first().fill(startDate.toString());
}); await page.locator('input[type="text"]').nth(1).click();
test('Visual - Default Condition Widget @unstable', async ({ page, theme }) => { // verify error msg for start time (unable to capture snapshot of popup)
test.info().annotations.push({ await percySnapshot(page, `Start time error (theme: '${theme}')`);
type: 'issue',
description: 'https://github.com/nasa/openmct/issues/5349'
});
await createDomainObjectWithDefaults(page, { type: 'Condition Widget' }); startDate = year - 1 + startDate.substring(4);
await page.locator('input[type="text"]').first().fill(startDate.toString());
// Take a snapshot of the newly created Condition Widget object endDate = year - 2 + endDate.substring(4);
await percySnapshot(page, `Default Condition Widget (theme: '${theme}')`); await page.locator('input[type="text"]').nth(1).fill(endDate.toString());
});
test('Visual - Time Conductor start time is less than end time', async ({ page, theme }) => { await page.locator('input[type="text"]').first().click();
const year = new Date().getFullYear();
let startDate = 'xxxx-01-01 01:00:00.000Z'; // verify error msg for end time (unable to capture snapshot of popup)
startDate = year + startDate.substring(4); await percySnapshot(page, `End time error (theme: '${theme}')`);
});
let endDate = 'xxxx-01-01 02:00:00.000Z'; test('Visual - Sine Wave Generator Form', async ({ page, theme }) => {
endDate = year + endDate.substring(4); //Click the Create button
await page.click('button:has-text("Create")');
await page.locator('input[type="text"]').nth(1).fill(endDate.toString()); // Click text=Sine Wave Generator
await page.locator('input[type="text"]').first().fill(startDate.toString()); await page.click('text=Sine Wave Generator');
// verify no error msg await percySnapshot(page, `Default Sine Wave Generator Form (theme: '${theme}')`);
await percySnapshot(page, `Default Time conductor (theme: '${theme}')`);
startDate = (year + 1) + startDate.substring(4); await page.locator('.field.control.l-input-sm input').first().click();
await page.locator('input[type="text"]').first().fill(startDate.toString()); await page.locator('.field.control.l-input-sm input').first().fill('');
await page.locator('input[type="text"]').nth(1).click();
// verify error msg for start time (unable to capture snapshot of popup) // Validate red x mark
await percySnapshot(page, `Start time error (theme: '${theme}')`); await percySnapshot(page, `removed amplitude property value (theme: '${theme}')`);
});
startDate = (year - 1) + startDate.substring(4); test('Visual - Save Successful Banner @unstable', async ({ page, theme }) => {
await page.locator('input[type="text"]').first().fill(startDate.toString()); await createDomainObjectWithDefaults(page, { type: 'Timer' });
endDate = (year - 2) + endDate.substring(4); await page.locator('.c-message-banner__message').hover({ trial: true });
await page.locator('input[type="text"]').nth(1).fill(endDate.toString()); await percySnapshot(page, `Banner message shown (theme: '${theme}')`);
await page.locator('input[type="text"]').first().click(); //Wait until Save Banner is gone
await page.locator('.c-message-banner__close-button').click();
await page.waitForSelector('.c-message-banner__message', { state: 'detached' });
await percySnapshot(page, `Banner message gone (theme: '${theme}')`);
});
// verify error msg for end time (unable to capture snapshot of popup) test('Visual - Display Layout Icon is correct', async ({ page, theme }) => {
await percySnapshot(page, `End time error (theme: '${theme}')`); //Click the Create button
}); await page.click('button:has-text("Create")');
test('Visual - Sine Wave Generator Form', async ({ page, theme }) => { //Hover on Display Layout option.
//Click the Create button await page.locator('text=Display Layout').hover();
await page.click('button:has-text("Create")'); await percySnapshot(page, `Display Layout Create Menu (theme: '${theme}')`);
});
// Click text=Sine Wave Generator test('Visual - Default Gauge is correct @unstable', async ({ page, theme }) => {
await page.click('text=Sine Wave Generator'); await createDomainObjectWithDefaults(page, { type: 'Gauge' });
await percySnapshot(page, `Default Sine Wave Generator Form (theme: '${theme}')`); // Take a snapshot of the newly created Gauge object
await percySnapshot(page, `Default Gauge (theme: '${theme}')`);
await page.locator('.field.control.l-input-sm input').first().click(); });
await page.locator('.field.control.l-input-sm input').first().fill('');
// Validate red x mark
await percySnapshot(page, `removed amplitude property value (theme: '${theme}')`);
});
test('Visual - Save Successful Banner @unstable', async ({ page, theme }) => {
await createDomainObjectWithDefaults(page, { type: 'Timer' });
await page.locator('.c-message-banner__message').hover({ trial: true });
await percySnapshot(page, `Banner message shown (theme: '${theme}')`);
//Wait until Save Banner is gone
await page.locator('.c-message-banner__close-button').click();
await page.waitForSelector('.c-message-banner__message', { state: 'detached'});
await percySnapshot(page, `Banner message gone (theme: '${theme}')`);
});
test('Visual - Display Layout Icon is correct', async ({ page, theme }) => {
//Click the Create button
await page.click('button:has-text("Create")');
//Hover on Display Layout option.
await page.locator('text=Display Layout').hover();
await percySnapshot(page, `Display Layout Create Menu (theme: '${theme}')`);
});
test('Visual - Default Gauge is correct @unstable', async ({ page, theme }) => {
await createDomainObjectWithDefaults(page, { type: 'Gauge' });
// Take a snapshot of the newly created Gauge object
await percySnapshot(page, `Default Gauge (theme: '${theme}')`);
});
}); });

View File

@ -27,51 +27,64 @@ const percySnapshot = require('@percy/playwright');
const utils = require('../../helper/faultUtils'); const utils = require('../../helper/faultUtils');
test.describe('The Fault Management Plugin Visual Test', () => { test.describe('The Fault Management Plugin Visual Test', () => {
test('icon test', async ({ page, theme }) => {
test('icon test', async ({ page, theme }) => { await page.addInitScript({
await page.addInitScript({ path: path.join(__dirname, '../../helper/', 'addInitFaultManagementPlugin.js') }); path: path.join(__dirname, '../../helper/', 'addInitFaultManagementPlugin.js')
await page.goto('./', { waitUntil: 'networkidle' });
await percySnapshot(page, `Fault Management icon appears in tree (theme: '${theme}')`);
}); });
await page.goto('./', { waitUntil: 'networkidle' });
test('fault list and acknowledged faults', async ({ page, theme }) => { await percySnapshot(page, `Fault Management icon appears in tree (theme: '${theme}')`);
await utils.navigateToFaultManagementWithStaticExample(page); });
await percySnapshot(page, `Shows a list of faults in the standard view (theme: '${theme}')`); test('fault list and acknowledged faults', async ({ page, theme }) => {
await utils.navigateToFaultManagementWithStaticExample(page);
await utils.acknowledgeFault(page, 1); await percySnapshot(page, `Shows a list of faults in the standard view (theme: '${theme}')`);
await utils.changeViewTo(page, 'acknowledged');
await percySnapshot(page, `Acknowledged faults, have a checkmark on the fault icon and appear in the acknowldeged view (theme: '${theme}')`); await utils.acknowledgeFault(page, 1);
}); await utils.changeViewTo(page, 'acknowledged');
test('shelved faults', async ({ page, theme }) => { await percySnapshot(
await utils.navigateToFaultManagementWithStaticExample(page); page,
`Acknowledged faults, have a checkmark on the fault icon and appear in the acknowldeged view (theme: '${theme}')`
);
});
await utils.shelveFault(page, 1); test('shelved faults', async ({ page, theme }) => {
await utils.changeViewTo(page, 'shelved'); await utils.navigateToFaultManagementWithStaticExample(page);
await percySnapshot(page, `Shelved faults appear in the shelved view (theme: '${theme}')`); await utils.shelveFault(page, 1);
await utils.changeViewTo(page, 'shelved');
await utils.openFaultRowMenu(page, 1); await percySnapshot(page, `Shelved faults appear in the shelved view (theme: '${theme}')`);
await percySnapshot(page, `Shelved faults have a 3-dot menu with Unshelve option enabled (theme: '${theme}')`); await utils.openFaultRowMenu(page, 1);
});
test('3-dot menu for fault', async ({ page, theme }) => { await percySnapshot(
await utils.navigateToFaultManagementWithStaticExample(page); page,
`Shelved faults have a 3-dot menu with Unshelve option enabled (theme: '${theme}')`
);
});
await utils.openFaultRowMenu(page, 1); test('3-dot menu for fault', async ({ page, theme }) => {
await utils.navigateToFaultManagementWithStaticExample(page);
await percySnapshot(page, `Faults have a 3-dot menu with Acknowledge, Shelve and Unshelve (Unshelve is disabled) options (theme: '${theme}')`); await utils.openFaultRowMenu(page, 1);
});
test('ability to acknowledge or shelve', async ({ page, theme }) => { await percySnapshot(
await utils.navigateToFaultManagementWithStaticExample(page); page,
`Faults have a 3-dot menu with Acknowledge, Shelve and Unshelve (Unshelve is disabled) options (theme: '${theme}')`
);
});
await utils.selectFaultItem(page, 1); test('ability to acknowledge or shelve', async ({ page, theme }) => {
await utils.navigateToFaultManagementWithStaticExample(page);
await percySnapshot(page, `Selected faults highlight the ability to Acknowledge or Shelve above the fault list (theme: '${theme}')`); await utils.selectFaultItem(page, 1);
});
await percySnapshot(
page,
`Selected faults highlight the ability to Acknowledge or Shelve above the fault list (theme: '${theme}')`
);
});
}); });

View File

@ -25,50 +25,54 @@ const percySnapshot = require('@percy/playwright');
const { createDomainObjectWithDefaults } = require('../../appActions'); const { createDomainObjectWithDefaults } = require('../../appActions');
test.describe('Visual - LAD Table', () => { test.describe('Visual - LAD Table', () => {
/** @type {import('@playwright/test').Locator} */ /** @type {import('@playwright/test').Locator} */
let ladTable; let ladTable;
test.beforeEach(async ({ page }) => { test.beforeEach(async ({ page }) => {
await page.goto('./', { waitUntil: 'domcontentloaded' }); await page.goto('./', { waitUntil: 'domcontentloaded' });
// Create LAD Table // Create LAD Table
ladTable = await createDomainObjectWithDefaults(page, { ladTable = await createDomainObjectWithDefaults(page, {
type: 'LAD Table', type: 'LAD Table',
name: 'LAD Table Test' name: 'LAD Table Test'
});
// Create SWG inside of LAD Table
await createDomainObjectWithDefaults(page, {
type: 'Sine Wave Generator',
name: 'SWG4LAD Table Test',
parent: ladTable.uuid
});
//Modify SWG to create a really stable SWG
await page.locator('button[title="More options"]').click();
await page.getByRole('menuitem', { name: ' Edit Properties...' }).click();
//Forgive me, padre
await page.getByRole('spinbutton', { name: 'Data Rate (hz)' }).fill('0');
await page.getByRole('spinbutton', { name: 'Period' }).fill('0');
await page.getByRole('button', { name: 'Save' }).click();
}); });
test('Toggled column widths behave accordingly', async ({ page, theme }) => { // Create SWG inside of LAD Table
await createDomainObjectWithDefaults(page, {
await page.goto(ladTable.url); type: 'Sine Wave Generator',
//Close panes for visual consistency name: 'SWG4LAD Table Test',
await page.getByTitle('Collapse Inspect Pane').click(); parent: ladTable.uuid
await page.getByTitle('Collapse Browse Pane').click();
await expect(page.locator('button[title="Expand Columns"]')).toBeVisible();
await percySnapshot(page, `LAD Table w/ Sine Wave Generator columns autosized (theme: ${theme})`);
await page.locator('button[title="Expand Columns"]').click();
await expect(page.locator('button[title="Autosize Columns"]')).toBeVisible();
await percySnapshot(page, `LAD Table w/ Sine Wave Generator columns expanded (theme: ${theme})`);
}); });
//Modify SWG to create a really stable SWG
await page.locator('button[title="More options"]').click();
await page.getByRole('menuitem', { name: ' Edit Properties...' }).click();
//Forgive me, padre
await page.getByRole('spinbutton', { name: 'Data Rate (hz)' }).fill('0');
await page.getByRole('spinbutton', { name: 'Period' }).fill('0');
await page.getByRole('button', { name: 'Save' }).click();
});
test('Toggled column widths behave accordingly', async ({ page, theme }) => {
await page.goto(ladTable.url);
//Close panes for visual consistency
await page.getByTitle('Collapse Inspect Pane').click();
await page.getByTitle('Collapse Browse Pane').click();
await expect(page.locator('button[title="Expand Columns"]')).toBeVisible();
await percySnapshot(
page,
`LAD Table w/ Sine Wave Generator columns autosized (theme: ${theme})`
);
await page.locator('button[title="Expand Columns"]').click();
await expect(page.locator('button[title="Autosize Columns"]')).toBeVisible();
await percySnapshot(
page,
`LAD Table w/ Sine Wave Generator columns expanded (theme: ${theme})`
);
});
}); });

View File

@ -25,27 +25,26 @@ const percySnapshot = require('@percy/playwright');
const { expandTreePaneItemByName, createDomainObjectWithDefaults } = require('../../appActions'); const { expandTreePaneItemByName, createDomainObjectWithDefaults } = require('../../appActions');
test.describe('Visual - Notebook', () => { test.describe('Visual - Notebook', () => {
test('Accepts dropped objects as embeds @unstable', async ({ page, theme, openmctConfig }) => { test('Accepts dropped objects as embeds @unstable', async ({ page, theme, openmctConfig }) => {
const { myItemsFolderName } = openmctConfig; const { myItemsFolderName } = openmctConfig;
await page.goto('./#/browse/mine', { waitUntil: 'networkidle' }); await page.goto('./#/browse/mine', { waitUntil: 'networkidle' });
// Create Notebook
const notebook = await createDomainObjectWithDefaults(page, {
type: 'Notebook',
name: "Embed Test Notebook"
});
// Create Overlay Plot
await createDomainObjectWithDefaults(page, {
type: 'Overlay Plot',
name: "Dropped Overlay Plot"
});
await expandTreePaneItemByName(page, myItemsFolderName);
await page.goto(notebook.url);
await page.dragAndDrop('role=treeitem[name=/Dropped Overlay Plot/]', '.c-notebook__drag-area');
await percySnapshot(page, `Notebook w/ dropped embed (theme: ${theme})`);
// Create Notebook
const notebook = await createDomainObjectWithDefaults(page, {
type: 'Notebook',
name: 'Embed Test Notebook'
}); });
// Create Overlay Plot
await createDomainObjectWithDefaults(page, {
type: 'Overlay Plot',
name: 'Dropped Overlay Plot'
});
await expandTreePaneItemByName(page, myItemsFolderName);
await page.goto(notebook.url);
await page.dragAndDrop('role=treeitem[name=/Dropped Overlay Plot/]', '.c-notebook__drag-area');
await percySnapshot(page, `Notebook w/ dropped embed (theme: ${theme})`);
});
}); });

View File

@ -28,31 +28,36 @@ const { test, expect } = require('../../pluginFixtures');
const percySnapshot = require('@percy/playwright'); const percySnapshot = require('@percy/playwright');
const { createDomainObjectWithDefaults } = require('../../appActions'); const { createDomainObjectWithDefaults } = require('../../appActions');
test.describe('Visual - Check Notification Info Banner of \'Save successful\'', () => { test.describe("Visual - Check Notification Info Banner of 'Save successful'", () => {
test.beforeEach(async ({ page }) => { test.beforeEach(async ({ page }) => {
// Go to baseURL and Hide Tree // Go to baseURL and Hide Tree
await page.goto('./', { waitUntil: 'networkidle' }); await page.goto('./', { waitUntil: 'networkidle' });
}); });
test('Create a clock, click on \'Save successful\' banner and dismiss it', async ({ page, theme }) => { test("Create a clock, click on 'Save successful' banner and dismiss it", async ({
// Create a clock domain object page,
await createDomainObjectWithDefaults(page, { type: 'Clock' }); theme
// Verify there is a button with aria-label="Review 1 Notification" }) => {
expect(await page.locator('button[aria-label="Review 1 Notification"]').isVisible()).toBe(true); // Create a clock domain object
// Verify there is a button with aria-label="Clear all notifications" await createDomainObjectWithDefaults(page, { type: 'Clock' });
expect(await page.locator('button[aria-label="Clear all notifications"]').isVisible()).toBe(true); // Verify there is a button with aria-label="Review 1 Notification"
// Click on the div with role="alert" that has "Save successful" text expect(await page.locator('button[aria-label="Review 1 Notification"]').isVisible()).toBe(true);
await page.locator('div[role="alert"]:has-text("Save successful")').click(); // Verify there is a button with aria-label="Clear all notifications"
// Verify there is a div with role="dialog" expect(await page.locator('button[aria-label="Clear all notifications"]').isVisible()).toBe(
expect(await page.locator('div[role="dialog"]').isVisible()).toBe(true); true
// Verify the div with role="dialog" contains text "Save successful" );
expect(await page.locator('div[role="dialog"]').innerText()).toContain('Save successful'); // Click on the div with role="alert" that has "Save successful" text
await percySnapshot(page, `Notification banner - ${theme}`); await page.locator('div[role="alert"]:has-text("Save successful")').click();
// Verify there is a button with text "Dismiss" // Verify there is a div with role="dialog"
expect(await page.locator('button:has-text("Dismiss")').isVisible()).toBe(true); expect(await page.locator('div[role="dialog"]').isVisible()).toBe(true);
// Click on button with text "Dismiss" // Verify the div with role="dialog" contains text "Save successful"
await page.locator('button:has-text("Dismiss")').click(); expect(await page.locator('div[role="dialog"]').innerText()).toContain('Save successful');
// Verify there is no div with role="dialog" await percySnapshot(page, `Notification banner - ${theme}`);
expect(await page.locator('div[role="dialog"]').isVisible()).toBe(false); // Verify there is a button with text "Dismiss"
}); expect(await page.locator('button:has-text("Dismiss")').isVisible()).toBe(true);
// Click on button with text "Dismiss"
await page.locator('button:has-text("Dismiss")').click();
// Verify there is no div with role="dialog"
expect(await page.locator('div[role="dialog"]').isVisible()).toBe(false);
});
}); });

View File

@ -29,71 +29,71 @@ const examplePlanSmall = require('../../test-data/examplePlans/ExamplePlan_Small
const snapshotScope = '.l-shell__pane-main .l-pane__contents'; const snapshotScope = '.l-shell__pane-main .l-pane__contents';
test.describe('Visual - Planning', () => { test.describe('Visual - Planning', () => {
test.beforeEach(async ({ page }) => { test.beforeEach(async ({ page }) => {
await page.goto('./', { waitUntil: 'domcontentloaded' }); await page.goto('./', { waitUntil: 'domcontentloaded' });
});
test('Plan View', async ({ page, theme }) => {
const plan = await createPlanFromJSON(page, {
name: 'Plan Visual Test',
json: examplePlanSmall
}); });
test('Plan View', async ({ page, theme }) => { await setBoundsToSpanAllActivities(page, examplePlanSmall, plan.url);
const plan = await createPlanFromJSON(page, { await percySnapshot(page, `Plan View (theme: ${theme})`, {
name: 'Plan Visual Test', scope: snapshotScope
json: examplePlanSmall });
}); });
await setBoundsToSpanAllActivities(page, examplePlanSmall, plan.url); test('Plan View w/ draft status', async ({ page, theme }) => {
await percySnapshot(page, `Plan View (theme: ${theme})`, { const plan = await createPlanFromJSON(page, {
scope: snapshotScope name: 'Plan Visual Test (Draft)',
}); json: examplePlanSmall
});
await page.goto('./#/browse/mine');
await setDraftStatusForPlan(page, plan);
await setBoundsToSpanAllActivities(page, examplePlanSmall, plan.url);
await percySnapshot(page, `Plan View w/ draft status (theme: ${theme})`, {
scope: snapshotScope
});
});
test('Gantt Chart View', async ({ page, theme }) => {
const ganttChart = await createDomainObjectWithDefaults(page, {
type: 'Gantt Chart',
name: 'Gantt Chart Visual Test'
});
await createPlanFromJSON(page, {
json: examplePlanSmall,
parent: ganttChart.uuid
});
await setBoundsToSpanAllActivities(page, examplePlanSmall, ganttChart.url);
await percySnapshot(page, `Gantt Chart View (theme: ${theme})`, {
scope: snapshotScope
});
});
test('Gantt Chart View w/ draft status', async ({ page, theme }) => {
const ganttChart = await createDomainObjectWithDefaults(page, {
type: 'Gantt Chart',
name: 'Gantt Chart Visual Test (Draft)'
});
const plan = await createPlanFromJSON(page, {
json: examplePlanSmall,
parent: ganttChart.uuid
}); });
test('Plan View w/ draft status', async ({ page, theme }) => { await setDraftStatusForPlan(page, plan);
const plan = await createPlanFromJSON(page, {
name: 'Plan Visual Test (Draft)',
json: examplePlanSmall
});
await page.goto('./#/browse/mine');
await setDraftStatusForPlan(page, plan); await page.goto('./#/browse/mine');
await setBoundsToSpanAllActivities(page, examplePlanSmall, plan.url); await setBoundsToSpanAllActivities(page, examplePlanSmall, ganttChart.url);
await percySnapshot(page, `Plan View w/ draft status (theme: ${theme})`, { await percySnapshot(page, `Gantt Chart View w/ draft status (theme: ${theme})`, {
scope: snapshotScope scope: snapshotScope
});
});
test('Gantt Chart View', async ({ page, theme }) => {
const ganttChart = await createDomainObjectWithDefaults(page, {
type: 'Gantt Chart',
name: 'Gantt Chart Visual Test'
});
await createPlanFromJSON(page, {
json: examplePlanSmall,
parent: ganttChart.uuid
});
await setBoundsToSpanAllActivities(page, examplePlanSmall, ganttChart.url);
await percySnapshot(page, `Gantt Chart View (theme: ${theme})`, {
scope: snapshotScope
});
});
test('Gantt Chart View w/ draft status', async ({ page, theme }) => {
const ganttChart = await createDomainObjectWithDefaults(page, {
type: 'Gantt Chart',
name: 'Gantt Chart Visual Test (Draft)'
});
const plan = await createPlanFromJSON(page, {
json: examplePlanSmall,
parent: ganttChart.uuid
});
await setDraftStatusForPlan(page, plan);
await page.goto('./#/browse/mine');
await setBoundsToSpanAllActivities(page, examplePlanSmall, ganttChart.url);
await percySnapshot(page, `Gantt Chart View w/ draft status (theme: ${theme})`, {
scope: snapshotScope
});
}); });
});
}); });
/** /**
@ -102,7 +102,7 @@ test.describe('Visual - Planning', () => {
* @param {import('../../appActions').CreatedObjectInfo} plan * @param {import('../../appActions').CreatedObjectInfo} plan
*/ */
async function setDraftStatusForPlan(page, plan) { async function setDraftStatusForPlan(page, plan) {
await page.evaluate(async (planObject) => { await page.evaluate(async (planObject) => {
await window.openmct.status.set(planObject.uuid, 'draft'); await window.openmct.status.set(planObject.uuid, 'draft');
}, plan); }, plan);
} }

View File

@ -30,55 +30,62 @@ const { createDomainObjectWithDefaults } = require('../../appActions');
const percySnapshot = require('@percy/playwright'); const percySnapshot = require('@percy/playwright');
test.describe('Grand Search', () => { test.describe('Grand Search', () => {
test.beforeEach(async ({ page, theme }) => { test.beforeEach(async ({ page, theme }) => {
//Go to baseURL and Hide Tree //Go to baseURL and Hide Tree
await page.goto('./#/browse/mine?hideTree=true', { waitUntil: 'networkidle' }); await page.goto('./#/browse/mine?hideTree=true', { waitUntil: 'networkidle' });
});
test.use({
clockOptions: {
now: 0, //Set browser clock to UNIX Epoch
shouldAdvanceTime: false //Don't advance the clock
}
});
//This needs to be rewritten to use a non clock or non display layout object
test('Can search for objects, and subsequent search dropdown behaves properly @unstable', async ({
page,
theme
}) => {
// await createDomainObjectWithDefaults(page, 'Display Layout');
// await page.locator('text=Snapshot Save and Finish Editing Save and Continue Editing >> button').nth(1).click();
// await page.locator('text=Save and Finish Editing').click();
const folder1 = 'Folder1';
await createDomainObjectWithDefaults(page, {
type: 'Folder',
name: folder1
}); });
test.use({
clockOptions: {
now: 0, //Set browser clock to UNIX Epoch
shouldAdvanceTime: false //Don't advance the clock
}
});
//This needs to be rewritten to use a non clock or non display layout object
test('Can search for objects, and subsequent search dropdown behaves properly @unstable', async ({ page, theme }) => {
// await createDomainObjectWithDefaults(page, 'Display Layout');
// await page.locator('text=Snapshot Save and Finish Editing Save and Continue Editing >> button').nth(1).click();
// await page.locator('text=Save and Finish Editing').click();
const folder1 = 'Folder1';
await createDomainObjectWithDefaults(page, {
type: 'Folder',
name: folder1
});
// Click [aria-label="OpenMCT Search"] input[type="search"] // Click [aria-label="OpenMCT Search"] input[type="search"]
await page.locator('[aria-label="OpenMCT Search"] input[type="search"]').click(); await page.locator('[aria-label="OpenMCT Search"] input[type="search"]').click();
// Fill [aria-label="OpenMCT Search"] input[type="search"] // Fill [aria-label="OpenMCT Search"] input[type="search"]
await page.locator('[aria-label="OpenMCT Search"] input[type="search"]').fill(folder1); await page.locator('[aria-label="OpenMCT Search"] input[type="search"]').fill(folder1);
await expect(page.locator('[aria-label="Search Result"]')).toContainText(folder1); await expect(page.locator('[aria-label="Search Result"]')).toContainText(folder1);
await percySnapshot(page, 'Searching for Folder Object'); await percySnapshot(page, 'Searching for Folder Object');
await page.locator('[aria-label="OpenMCT Search"] [aria-label="Search Input"]').click(); await page.locator('[aria-label="OpenMCT Search"] [aria-label="Search Input"]').click();
await page.locator('[aria-label="Unnamed Clock clock result"] >> text=Unnamed Clock').click(); await page.locator('[aria-label="Unnamed Clock clock result"] >> text=Unnamed Clock').click();
await percySnapshot(page, 'Preview for clock should display when editing enabled and search item clicked'); await percySnapshot(
page,
'Preview for clock should display when editing enabled and search item clicked'
);
await page.locator('[aria-label="Close"]').click(); await page.locator('[aria-label="Close"]').click();
await percySnapshot(page, 'Search should still be showing after preview closed'); await percySnapshot(page, 'Search should still be showing after preview closed');
await page.locator('text=Snapshot Save and Finish Editing Save and Continue Editing >> button').nth(1).click(); await page
.locator('text=Snapshot Save and Finish Editing Save and Continue Editing >> button')
.nth(1)
.click();
await page.locator('text=Save and Finish Editing').click(); await page.locator('text=Save and Finish Editing').click();
await page.locator('[aria-label="OpenMCT Search"] [aria-label="Search Input"]').click(); await page.locator('[aria-label="OpenMCT Search"] [aria-label="Search Input"]').click();
await page.locator('[aria-label="OpenMCT Search"] [aria-label="Search Input"]').fill('Cl'); await page.locator('[aria-label="OpenMCT Search"] [aria-label="Search Input"]').fill('Cl');
await Promise.all([ await Promise.all([page.waitForNavigation(), page.locator('text=Unnamed Clock').click()]);
page.waitForNavigation(), await percySnapshot(
page.locator('text=Unnamed Clock').click() page,
]); `Clicking on search results should navigate to them if not editing (theme: '${theme}')`
await percySnapshot(page, `Clicking on search results should navigate to them if not editing (theme: '${theme}')`); );
});
});
}); });

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