Compare commits
54 Commits
visual-tes
...
fix-exampl
Author | SHA1 | Date | |
---|---|---|---|
b59ed09274 | |||
810d580b18 | |||
977792fae8 | |||
a69e300f1c | |||
17bc6cb722 | |||
b3d3465734 | |||
fb0d74e87f | |||
a961d7e3bf | |||
5a06b51c5a | |||
ef8b353d01 | |||
6c5b925454 | |||
e91aba2e37 | |||
b18aa48141 | |||
d33da65dae | |||
e3adeb6a75 | |||
de3dad02b5 | |||
311ad0b87a | |||
f98eb31956 | |||
1671a585fb | |||
a3fb84ad43 | |||
a5c6b141a6 | |||
46823ec225 | |||
5b4ee1949f | |||
7e926ccbb7 | |||
6e264517f8 | |||
986da5782b | |||
539138437b | |||
493b31d0b9 | |||
d68ac31ab5 | |||
f504ee29cc | |||
1d5ddc545e | |||
42085a4b70 | |||
b2b0837592 | |||
e305b46d88 | |||
fb396ac194 | |||
a01f21017f | |||
b7b9ccbe65 | |||
f189a4d602 | |||
4027eae299 | |||
d4695178bc | |||
5fc5c13314 | |||
ceeb761d94 | |||
10eb749d32 | |||
faed27c143 | |||
18e976ad12 | |||
64862634f3 | |||
cb4c59a464 | |||
5f0bd10c61 | |||
8c2558bfe0 | |||
0eadc7a4ae | |||
cad4652a08 | |||
8379f2d073 | |||
8ed112a4a8 | |||
14c58c4410 |
@ -1,59 +1,33 @@
|
|||||||
version: 2.1
|
version: 2.1
|
||||||
|
orbs:
|
||||||
|
node: circleci/node@5.2.0
|
||||||
|
browser-tools: circleci/browser-tools@1.3.0
|
||||||
executors:
|
executors:
|
||||||
pw-focal-development:
|
pw-focal-development:
|
||||||
docker:
|
docker:
|
||||||
- image: mcr.microsoft.com/playwright:v1.39.0-focal
|
- image: mcr.microsoft.com/playwright:v1.44.0-focal
|
||||||
environment:
|
environment:
|
||||||
NODE_ENV: development # Needed to ensure 'dist' folder created and devDependencies installed
|
NODE_ENV: development # Needed to ensure 'dist' folder created and devDependencies installed
|
||||||
PERCY_POSTINSTALL_BROWSER: "true" # Needed to store the percy browser in cache deps
|
PERCY_POSTINSTALL_BROWSER: 'true' # Needed to store the percy browser in cache deps
|
||||||
PERCY_LOGLEVEL: "debug" # Enable DEBUG level logging for Percy (Issue: https://github.com/nasa/openmct/issues/5742)
|
PERCY_LOGLEVEL: 'debug' # Enable DEBUG level logging for Percy (Issue: https://github.com/nasa/openmct/issues/5742)
|
||||||
|
PERCY_PARALLEL_TOTAL: 2
|
||||||
ubuntu:
|
ubuntu:
|
||||||
machine:
|
machine:
|
||||||
image: ubuntu-2204:current
|
image: ubuntu-2204:current
|
||||||
docker_layer_caching: true
|
docker_layer_caching: true
|
||||||
parameters:
|
|
||||||
BUST_CACHE:
|
|
||||||
description: "Set this with the CircleCI UI Trigger Workflow button (boolean = true) to bust the cache!"
|
|
||||||
default: false
|
|
||||||
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.'
|
||||||
parameters:
|
parameters:
|
||||||
node-version:
|
node-version:
|
||||||
type: string
|
type: string
|
||||||
steps:
|
steps:
|
||||||
- checkout
|
- checkout
|
||||||
- restore_cache_cmd:
|
|
||||||
node-version: << parameters.node-version >>
|
|
||||||
- node/install:
|
- node/install:
|
||||||
node-version: << parameters.node-version >>
|
node-version: << parameters.node-version >>
|
||||||
- run: npm install --no-audit --progress=false
|
- node/install-packages
|
||||||
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"
|
|
||||||
parameters:
|
|
||||||
node-version:
|
|
||||||
type: string
|
|
||||||
steps:
|
|
||||||
- when:
|
|
||||||
condition:
|
|
||||||
equal: [false, << pipeline.parameters.BUST_CACHE >>]
|
|
||||||
steps:
|
|
||||||
- restore_cache:
|
|
||||||
key: deps--{{ arch }}--{{ .Branch }}--<< parameters.node-version >>--{{ checksum "package.json" }}-{{ checksum ".circleci/config.yml" }}
|
|
||||||
save_cache_cmd:
|
|
||||||
description: "Custom command for saving cache."
|
|
||||||
parameters:
|
|
||||||
node-version:
|
|
||||||
type: string
|
|
||||||
steps:
|
|
||||||
- save_cache:
|
|
||||||
key: deps--{{ arch }}--{{ .Branch }}--<< parameters.node-version >>--{{ checksum "package.json" }}-{{ checksum ".circleci/config.yml" }}
|
|
||||||
paths:
|
|
||||||
- ~/.npm
|
|
||||||
- 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,16 +38,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:
|
|
||||||
node: circleci/node@5.1.0
|
|
||||||
browser-tools: circleci/browser-tools@1.3.0
|
|
||||||
jobs:
|
jobs:
|
||||||
npm-audit:
|
npm-audit:
|
||||||
parameters:
|
parameters:
|
||||||
@ -111,8 +82,6 @@ jobs:
|
|||||||
TESTFILES=$(circleci tests glob "src/**/*Spec.js")
|
TESTFILES=$(circleci tests glob "src/**/*Spec.js")
|
||||||
echo "$TESTFILES" | circleci tests run --command="xargs npm run test" --verbose
|
echo "$TESTFILES" | circleci tests run --command="xargs npm run test" --verbose
|
||||||
- run: npm run cov:unit:publish
|
- run: npm run cov:unit:publish
|
||||||
- save_cache_cmd:
|
|
||||||
node-version: <<parameters.node-version>>
|
|
||||||
- store_test_results:
|
- store_test_results:
|
||||||
path: dist/reports/tests/
|
path: dist/reports/tests/
|
||||||
- store_artifacts:
|
- store_artifacts:
|
||||||
@ -133,7 +102,7 @@ jobs:
|
|||||||
node-version: lts/hydrogen
|
node-version: lts/hydrogen
|
||||||
- when: #Only install chrome-beta when running the 'full' suite to save $$$
|
- when: #Only install chrome-beta when running the 'full' suite to save $$$
|
||||||
condition:
|
condition:
|
||||||
equal: ["full", <<parameters.suite>>]
|
equal: ['full', <<parameters.suite>>]
|
||||||
steps:
|
steps:
|
||||||
- run: npx playwright install chrome-beta
|
- run: npx playwright install chrome-beta
|
||||||
- run:
|
- run:
|
||||||
@ -190,7 +159,7 @@ jobs:
|
|||||||
steps:
|
steps:
|
||||||
- build_and_install:
|
- build_and_install:
|
||||||
node-version: lts/hydrogen
|
node-version: lts/hydrogen
|
||||||
- run: npx playwright@1.39.0 install #Necessary for bare ubuntu machine
|
- run: npx playwright@1.44.0 install #Necessary for bare ubuntu machine
|
||||||
- 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
|
||||||
@ -252,14 +221,15 @@ jobs:
|
|||||||
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-a11y-tests:
|
visual-a11y:
|
||||||
parameters:
|
parameters:
|
||||||
suite:
|
suite:
|
||||||
type: string # ci or full
|
type: string # ci or full
|
||||||
executor: pw-focal-development
|
executor: pw-focal-development
|
||||||
|
parallelism: 2
|
||||||
steps:
|
steps:
|
||||||
- build_and_install:
|
- build_and_install:
|
||||||
node-version: lts/hydrogen
|
node-version: lts/iron
|
||||||
- run: npm run test:e2e:visual:<<parameters.suite>>
|
- run: npm run test:e2e:visual:<<parameters.suite>>
|
||||||
- store_test_results:
|
- store_test_results:
|
||||||
path: test-results/results.xml
|
path: test-results/results.xml
|
||||||
@ -286,8 +256,8 @@ workflows:
|
|||||||
name: e2e-stable
|
name: e2e-stable
|
||||||
suite: stable
|
suite: stable
|
||||||
- e2e-mobile
|
- e2e-mobile
|
||||||
- visual-a11y-tests:
|
- visual-a11y:
|
||||||
name: visual-a11y-test-ci
|
name: visual-a11y-ci
|
||||||
suite: ci
|
suite: ci
|
||||||
|
|
||||||
the-nightly: #These jobs do not run on PRs, but against master at night
|
the-nightly: #These jobs do not run on PRs, but against master at night
|
||||||
@ -306,13 +276,13 @@ workflows:
|
|||||||
- e2e-mobile
|
- e2e-mobile
|
||||||
- perf-test
|
- perf-test
|
||||||
- mem-test
|
- mem-test
|
||||||
- visual-a11y-tests:
|
- visual-a11y:
|
||||||
name: visual-a11y-test-nightly
|
name: visual-a11y-nightly
|
||||||
suite: full
|
suite: full
|
||||||
- e2e-couchdb
|
- e2e-couchdb
|
||||||
triggers:
|
triggers:
|
||||||
- schedule:
|
- schedule:
|
||||||
cron: "0 0 * * *"
|
cron: '0 0 * * *'
|
||||||
filters:
|
filters:
|
||||||
branches:
|
branches:
|
||||||
only:
|
only:
|
||||||
|
@ -497,7 +497,8 @@
|
|||||||
"checksnapshots",
|
"checksnapshots",
|
||||||
"specced",
|
"specced",
|
||||||
"composables",
|
"composables",
|
||||||
"countup"
|
"countup",
|
||||||
|
"darkmatter"
|
||||||
],
|
],
|
||||||
"dictionaries": ["npm", "softwareTerms", "node", "html", "css", "bash", "en_US", "en-gb", "misc"],
|
"dictionaries": ["npm", "softwareTerms", "node", "html", "css", "bash", "en_US", "en-gb", "misc"],
|
||||||
"ignorePaths": [
|
"ignorePaths": [
|
||||||
|
@ -1,10 +1,13 @@
|
|||||||
const LEGACY_FILES = ['example/**'];
|
const LEGACY_FILES = ['example/**'];
|
||||||
module.exports = {
|
/** @type {import('eslint').Linter.Config} */
|
||||||
|
const config = {
|
||||||
env: {
|
env: {
|
||||||
browser: true,
|
browser: true,
|
||||||
es6: true,
|
es2024: true,
|
||||||
jasmine: true,
|
jasmine: true,
|
||||||
amd: true
|
node: true,
|
||||||
|
worker: true,
|
||||||
|
serviceworker: true
|
||||||
},
|
},
|
||||||
globals: {
|
globals: {
|
||||||
_: 'readonly'
|
_: 'readonly'
|
||||||
@ -23,10 +26,11 @@ module.exports = {
|
|||||||
parser: '@babel/eslint-parser',
|
parser: '@babel/eslint-parser',
|
||||||
requireConfigFile: false,
|
requireConfigFile: false,
|
||||||
allowImportExportEverywhere: true,
|
allowImportExportEverywhere: true,
|
||||||
ecmaVersion: 2015,
|
ecmaVersion: 'latest',
|
||||||
ecmaFeatures: {
|
ecmaFeatures: {
|
||||||
impliedStrict: true
|
impliedStrict: true
|
||||||
}
|
},
|
||||||
|
sourceType: 'module'
|
||||||
},
|
},
|
||||||
rules: {
|
rules: {
|
||||||
'simple-import-sort/imports': 'warn',
|
'simple-import-sort/imports': 'warn',
|
||||||
@ -152,7 +156,7 @@ module.exports = {
|
|||||||
cases: {
|
cases: {
|
||||||
pascalCase: true
|
pascalCase: true
|
||||||
},
|
},
|
||||||
ignore: ['^.*\\.js$']
|
ignore: ['^.*\\.(js|cjs|mjs)$']
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
'vue/first-attribute-linebreak': 'error',
|
'vue/first-attribute-linebreak': 'error',
|
||||||
@ -179,3 +183,5 @@ module.exports = {
|
|||||||
}
|
}
|
||||||
]
|
]
|
||||||
};
|
};
|
||||||
|
|
||||||
|
module.exports = config;
|
||||||
|
1
.github/PULL_REQUEST_TEMPLATE.md
vendored
@ -17,7 +17,6 @@ Closes <!--- Insert Issue Number(s) this PR addresses. Start by typing # will op
|
|||||||
* [ ] Has this been smoke tested?
|
* [ ] Has this been smoke tested?
|
||||||
* [ ] Have you associated this PR with a `type:` label? Note: this is not necessarily the same as the original issue.
|
* [ ] Have you associated this PR with a `type:` label? Note: this is not necessarily the same as the original issue.
|
||||||
* [ ] Have you associated a milestone with this PR? Note: leave blank if unsure.
|
* [ ] Have you associated a milestone with this PR? Note: leave blank if unsure.
|
||||||
* [ ] Is this a breaking change to be called out in the release notes?
|
|
||||||
* [ ] Testing instructions included in associated issue OR is this a dependency/testcase change?
|
* [ ] Testing instructions included in associated issue OR is this a dependency/testcase change?
|
||||||
|
|
||||||
### Reviewer Checklist
|
### Reviewer Checklist
|
||||||
|
4
.github/workflows/e2e-couchdb.yml
vendored
@ -28,7 +28,7 @@ jobs:
|
|||||||
restore-keys: |
|
restore-keys: |
|
||||||
${{ runner.os }}-node-
|
${{ runner.os }}-node-
|
||||||
|
|
||||||
- run: npm install --cache ~/.npm --no-audit --progress=false
|
- run: npm ci --no-audit --progress=false
|
||||||
|
|
||||||
- name: Login to DockerHub
|
- name: Login to DockerHub
|
||||||
uses: docker/login-action@v3
|
uses: docker/login-action@v3
|
||||||
@ -37,7 +37,7 @@ jobs:
|
|||||||
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||||
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||||
|
|
||||||
- run: npx playwright@1.39.0 install
|
- run: npx playwright@1.44.0 install
|
||||||
|
|
||||||
- name: Start CouchDB Docker Container and Init with Setup Scripts
|
- name: Start CouchDB Docker Container and Init with Setup Scripts
|
||||||
run: |
|
run: |
|
||||||
|
4
.github/workflows/e2e-flakefinder.yml
vendored
@ -30,8 +30,8 @@ jobs:
|
|||||||
restore-keys: |
|
restore-keys: |
|
||||||
${{ runner.os }}-node-
|
${{ runner.os }}-node-
|
||||||
|
|
||||||
- run: npx playwright@1.39.0 install
|
- run: npx playwright@1.44.0 install
|
||||||
- run: npm install --cache ~/.npm --no-audit --progress=false
|
- run: npm ci --no-audit --progress=false
|
||||||
|
|
||||||
- name: Run E2E Tests (Repeated 10 Times)
|
- name: Run E2E Tests (Repeated 10 Times)
|
||||||
run: npm run test:e2e:stable -- --retries=0 --repeat-each=10 --max-failures=50
|
run: npm run test:e2e:stable -- --retries=0 --repeat-each=10 --max-failures=50
|
||||||
|
4
.github/workflows/e2e-perf.yml
vendored
@ -28,8 +28,8 @@ jobs:
|
|||||||
restore-keys: |
|
restore-keys: |
|
||||||
${{ runner.os }}-node-
|
${{ runner.os }}-node-
|
||||||
|
|
||||||
- run: npx playwright@1.39.0 install
|
- run: npx playwright@1.44.0 install
|
||||||
- run: npm install --cache ~/.npm --no-audit --progress=false
|
- run: npm ci --no-audit --progress=false
|
||||||
- run: npm run test:perf:localhost
|
- run: npm run test:perf:localhost
|
||||||
- run: npm run test:perf:contract
|
- run: npm run test:perf:contract
|
||||||
- run: npm run test:perf:memory
|
- run: npm run test:perf:memory
|
||||||
|
4
.github/workflows/e2e-pr.yml
vendored
@ -33,9 +33,9 @@ jobs:
|
|||||||
restore-keys: |
|
restore-keys: |
|
||||||
${{ runner.os }}-node-
|
${{ runner.os }}-node-
|
||||||
|
|
||||||
- run: npx playwright@1.39.0 install
|
- run: npx playwright@1.44.0 install
|
||||||
- run: npx playwright install chrome-beta
|
- run: npx playwright install chrome-beta
|
||||||
- run: npm install --cache ~/.npm --no-audit --progress=false
|
- run: npm ci --no-audit --progress=false
|
||||||
- run: npm run test:e2e:full -- --max-failures=40
|
- run: npm run test:e2e:full -- --max-failures=40
|
||||||
- run: npm run cov:e2e:report || true
|
- run: npm run cov:e2e:report || true
|
||||||
- shell: bash
|
- shell: bash
|
||||||
|
4
.github/workflows/npm-prerelease.yml
vendored
@ -15,7 +15,7 @@ jobs:
|
|||||||
- uses: actions/setup-node@v4
|
- uses: actions/setup-node@v4
|
||||||
with:
|
with:
|
||||||
node-version: lts/hydrogen
|
node-version: lts/hydrogen
|
||||||
- run: npm install
|
- run: npm ci
|
||||||
- run: |
|
- run: |
|
||||||
echo "//registry.npmjs.org/:_authToken=$NODE_AUTH_TOKEN" >> ~/.npmrc
|
echo "//registry.npmjs.org/:_authToken=$NODE_AUTH_TOKEN" >> ~/.npmrc
|
||||||
npm whoami
|
npm whoami
|
||||||
@ -31,7 +31,7 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
node-version: lts/hydrogen
|
node-version: lts/hydrogen
|
||||||
registry-url: https://registry.npmjs.org/
|
registry-url: https://registry.npmjs.org/
|
||||||
- run: npm install
|
- run: npm ci
|
||||||
- run: npm publish --access=public --tag unstable
|
- run: npm publish --access=public --tag unstable
|
||||||
env:
|
env:
|
||||||
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
|
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
|
||||||
|
2
.github/workflows/pr-platform.yml
vendored
@ -45,7 +45,7 @@ jobs:
|
|||||||
restore-keys: |
|
restore-keys: |
|
||||||
${{ runner.os }}-${{ matrix.node_version }}-
|
${{ runner.os }}-${{ matrix.node_version }}-
|
||||||
|
|
||||||
- run: npm install --cache ~/.npm --no-audit --progress=false
|
- run: npm ci --no-audit --progress=false
|
||||||
|
|
||||||
- run: npm test
|
- run: npm test
|
||||||
|
|
||||||
|
2
.github/workflows/prcop.yml
vendored
@ -5,6 +5,8 @@ on:
|
|||||||
types:
|
types:
|
||||||
- labeled
|
- labeled
|
||||||
- unlabeled
|
- unlabeled
|
||||||
|
- milestoned
|
||||||
|
- demilestoned
|
||||||
- opened
|
- opened
|
||||||
- reopened
|
- reopened
|
||||||
- synchronize
|
- synchronize
|
||||||
|
4
.gitignore
vendored
@ -48,5 +48,5 @@ index.html.bak
|
|||||||
coverage
|
coverage
|
||||||
codecov
|
codecov
|
||||||
|
|
||||||
# :(
|
# Don't commit MacOS screenshots
|
||||||
package-lock.json
|
*-darwin.png
|
||||||
|
@ -22,9 +22,3 @@
|
|||||||
!index.html
|
!index.html
|
||||||
!openmct.js
|
!openmct.js
|
||||||
!SECURITY.md
|
!SECURITY.md
|
||||||
|
|
||||||
# Add e2e tests to npm package
|
|
||||||
!/e2e/**/*
|
|
||||||
|
|
||||||
# ... except our test-data folder files.
|
|
||||||
/e2e/test-data/*.json
|
|
||||||
|
3
.npmrc
@ -2,6 +2,3 @@ loglevel=warn
|
|||||||
|
|
||||||
#Prevent folks from ignoring an important error when building from source
|
#Prevent folks from ignoring an important error when building from source
|
||||||
engine-strict=true
|
engine-strict=true
|
||||||
|
|
||||||
# Dont include lockfile
|
|
||||||
package-lock=false
|
|
1
.vscode/extensions.json
vendored
@ -5,7 +5,6 @@
|
|||||||
// List of extensions which should be recommended for users of this workspace.
|
// List of extensions which should be recommended for users of this workspace.
|
||||||
"recommendations": [
|
"recommendations": [
|
||||||
"Vue.volar",
|
"Vue.volar",
|
||||||
"Vue.vscode-typescript-vue-plugin",
|
|
||||||
"dbaeumer.vscode-eslint",
|
"dbaeumer.vscode-eslint",
|
||||||
"rvest.vs-code-prettier-eslint"
|
"rvest.vs-code-prettier-eslint"
|
||||||
],
|
],
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
/*
|
/*
|
||||||
This is the OpenMCT common webpack file. It is imported by the other three webpack configurations:
|
This is the OpenMCT common webpack file. It is imported by the other three webpack configurations:
|
||||||
- webpack.prod.js - the production configuration for OpenMCT (default)
|
- webpack.prod.mjs - the production configuration for OpenMCT (default)
|
||||||
- webpack.dev.js - the development configuration for OpenMCT
|
- webpack.dev.mjs - the development configuration for OpenMCT
|
||||||
- webpack.coverage.js - imports webpack.dev.js and adds code coverage
|
- webpack.coverage.mjs - imports webpack.dev.js and adds code coverage
|
||||||
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.
|
||||||
*/
|
*/
|
||||||
@ -15,10 +15,11 @@ import CopyWebpackPlugin from 'copy-webpack-plugin';
|
|||||||
import MiniCssExtractPlugin from 'mini-css-extract-plugin';
|
import MiniCssExtractPlugin from 'mini-css-extract-plugin';
|
||||||
import { VueLoaderPlugin } from 'vue-loader';
|
import { VueLoaderPlugin } from 'vue-loader';
|
||||||
import webpack from 'webpack';
|
import webpack from 'webpack';
|
||||||
|
import { merge } from 'webpack-merge';
|
||||||
let gitRevision = 'error-retrieving-revision';
|
let gitRevision = 'error-retrieving-revision';
|
||||||
let gitBranch = 'error-retrieving-branch';
|
let gitBranch = 'error-retrieving-branch';
|
||||||
|
|
||||||
const packageDefinition = JSON.parse(fs.readFileSync(new URL('../package.json', import.meta.url)));
|
const { version } = JSON.parse(fs.readFileSync(new URL('../package.json', import.meta.url)));
|
||||||
|
|
||||||
try {
|
try {
|
||||||
gitRevision = execSync('git rev-parse HEAD').toString().trim();
|
gitRevision = execSync('git rev-parse HEAD').toString().trim();
|
||||||
@ -48,15 +49,18 @@ const config = {
|
|||||||
couchDBChangesFeed: './src/plugins/persistence/couch/CouchChangesFeed.js',
|
couchDBChangesFeed: './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',
|
||||||
|
darkmatterTheme: './src/plugins/themes/darkmatter-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: {
|
||||||
libraryExport: 'default',
|
name: 'openmct',
|
||||||
libraryTarget: 'umd',
|
type: 'umd',
|
||||||
|
export: 'default'
|
||||||
|
},
|
||||||
publicPath: '',
|
publicPath: '',
|
||||||
hashFunction: 'xxhash64',
|
hashFunction: 'xxhash64',
|
||||||
clean: true
|
clean: true
|
||||||
@ -81,7 +85,7 @@ const config = {
|
|||||||
},
|
},
|
||||||
plugins: [
|
plugins: [
|
||||||
new webpack.DefinePlugin({
|
new webpack.DefinePlugin({
|
||||||
__OPENMCT_VERSION__: `'${packageDefinition.version}'`,
|
__OPENMCT_VERSION__: `'${version}'`,
|
||||||
__OPENMCT_BUILD_DATE__: `'${new Date()}'`,
|
__OPENMCT_BUILD_DATE__: `'${new Date()}'`,
|
||||||
__OPENMCT_REVISION__: `'${gitRevision}'`,
|
__OPENMCT_REVISION__: `'${gitRevision}'`,
|
||||||
__OPENMCT_BUILD_BRANCH__: `'${gitBranch}'`,
|
__OPENMCT_BUILD_BRANCH__: `'${gitBranch}'`,
|
@ -1,15 +1,12 @@
|
|||||||
/*
|
/*
|
||||||
This file extends the webpack.dev.js config to add babel istanbul coverage.
|
This file extends the webpack.dev.mjs config to add babel istanbul coverage.
|
||||||
OpenMCT Continuous Integration servers use this configuration to add code coverage
|
OpenMCT Continuous Integration servers use this configuration to add code coverage
|
||||||
information to pull requests.
|
information to pull requests.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import config from './webpack.dev.js';
|
import config from './webpack.dev.mjs';
|
||||||
// eslint-disable-next-line no-undef
|
|
||||||
const CI = process.env.CI === 'true';
|
|
||||||
|
|
||||||
config.devtool = CI ? false : undefined;
|
|
||||||
|
|
||||||
|
config.devtool = 'source-map';
|
||||||
config.devServer.hot = false;
|
config.devServer.hot = false;
|
||||||
|
|
||||||
config.module.rules.push({
|
config.module.rules.push({
|
||||||
@ -19,7 +16,6 @@ config.module.rules.push({
|
|||||||
loader: 'babel-loader',
|
loader: 'babel-loader',
|
||||||
options: {
|
options: {
|
||||||
retainLines: true,
|
retainLines: true,
|
||||||
// eslint-disable-next-line no-undef
|
|
||||||
plugins: [
|
plugins: [
|
||||||
[
|
[
|
||||||
'babel-plugin-istanbul',
|
'babel-plugin-istanbul',
|
@ -1,14 +1,15 @@
|
|||||||
/*
|
/*
|
||||||
This configuration should be used for development purposes. It contains full source map, a
|
This configuration should be used for development purposes. It contains full source map, a
|
||||||
devServer (which be invoked using by `npm start`), and a non-minified Vue.js distribution.
|
devServer (which be invoked using by `npm start`), and a non-minified Vue.js distribution.
|
||||||
If OpenMCT is to be used for a production server, use webpack.prod.js instead.
|
If OpenMCT is to be used for a production server, use webpack.prod.mjs instead.
|
||||||
*/
|
*/
|
||||||
|
import { fileURLToPath } from 'node:url';
|
||||||
|
|
||||||
import path from 'path';
|
import path from 'path';
|
||||||
import webpack from 'webpack';
|
import webpack from 'webpack';
|
||||||
import { merge } from 'webpack-merge';
|
import { merge } from 'webpack-merge';
|
||||||
import { fileURLToPath } from 'node:url';
|
|
||||||
|
|
||||||
import common from './webpack.common.js';
|
import common from './webpack.common.mjs';
|
||||||
|
|
||||||
export default merge(common, {
|
export default merge(common, {
|
||||||
mode: 'development',
|
mode: 'development',
|
@ -6,7 +6,7 @@ It is the default webpack configuration.
|
|||||||
import webpack from 'webpack';
|
import webpack from 'webpack';
|
||||||
import { merge } from 'webpack-merge';
|
import { merge } from 'webpack-merge';
|
||||||
|
|
||||||
import common from './webpack.common.js';
|
import common from './webpack.common.mjs';
|
||||||
|
|
||||||
export default merge(common, {
|
export default merge(common, {
|
||||||
mode: 'production',
|
mode: 'production',
|
10
API.md
@ -1305,6 +1305,16 @@ View provider Example:
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## User API
|
||||||
|
|
||||||
|
Open MCT provides a User API which can be used to define providers for user information. The API
|
||||||
|
can be used to manage user information and roles.
|
||||||
|
|
||||||
|
### Example
|
||||||
|
|
||||||
|
Open MCT provides an example [user](example/exampleUser/exampleUserCreator.js) and [user provider](example/exampleUser/ExampleUserProvider.js) which
|
||||||
|
can be used as a starting point for creating a custom user provider.
|
||||||
|
|
||||||
## Visibility-Based Rendering in View Providers
|
## Visibility-Based Rendering in View Providers
|
||||||
|
|
||||||
To enhance performance and resource efficiency in OpenMCT, a visibility-based rendering feature has been added. This feature is designed to defer the execution of rendering logic for views that are not currently visible. It ensures that views are only updated when they are in the viewport, similar to how modern browsers handle rendering of inactive tabs but optimized for the OpenMCT tabbed display. It also works when views are scrolled outside the viewport (e.g., in a Display Layout).
|
To enhance performance and resource efficiency in OpenMCT, a visibility-based rendering feature has been added. This feature is designed to defer the execution of rendering logic for views that are not currently visible. It ensures that views are only updated when they are in the viewport, similar to how modern browsers handle rendering of inactive tabs but optimized for the OpenMCT tabbed display. It also works when views are scrolled outside the viewport (e.g., in a Display Layout).
|
||||||
|
@ -16,8 +16,6 @@ The [CodeQL GitHub Actions workflow](https://github.com/nasa/openmct/blob/master
|
|||||||
|
|
||||||
CodeQL is run for every pull-request in GitHub Actions.
|
CodeQL is run for every pull-request in GitHub Actions.
|
||||||
|
|
||||||
The project is also monitored by [LGTM](https://lgtm.com/projects/g/nasa/openmct/) and is available to public.
|
|
||||||
|
|
||||||
### ESLint
|
### ESLint
|
||||||
|
|
||||||
Static analysis is run for every push on the master branch and every pull request on all branches in Github Actions.
|
Static analysis is run for every push on the master branch and every pull request on all branches in Github Actions.
|
||||||
|
16
TESTING.md
@ -63,7 +63,7 @@ Once the file is generated, it can be published to codecov with
|
|||||||
### e2e
|
### e2e
|
||||||
The e2e line coverage is a bit more complex than the karma implementation. This is the general sequence of events:
|
The e2e line coverage is a bit more complex than the karma implementation. This is the general sequence of events:
|
||||||
|
|
||||||
1. Each e2e suite will start webpack with the ```npm run start:coverage``` command with config `webpack.coverage.js` and the `babel-plugin-istanbul` plugin to generate code coverage during e2e test execution using our custom [baseFixture](./baseFixtures.js).
|
1. Each e2e suite will start webpack with the ```npm run start:coverage``` command with config `webpack.coverage.mjs` and the `babel-plugin-istanbul` plugin to generate code coverage during e2e test execution using our custom [baseFixture](./baseFixtures.js).
|
||||||
1. During testcase execution, each e2e shard will generate its piece of the larger coverage suite. **This coverage file is not merged**. The raw coverage file is stored in a `.nyc_report` directory.
|
1. During testcase execution, each e2e shard will generate its piece of the larger coverage suite. **This coverage file is not merged**. The raw coverage file is stored in a `.nyc_report` directory.
|
||||||
1. [nyc](https://github.com/istanbuljs/nyc) converts this directory into a `lcov` file with the following command `npm run cov:e2e:report`
|
1. [nyc](https://github.com/istanbuljs/nyc) converts this directory into a `lcov` file with the following command `npm run cov:e2e:report`
|
||||||
1. Most of the tests are run in the '@stable' configuration and focus on chrome/ubuntu at a single resolution. This coverage is published to codecov with `npm run cov:e2e:stable:publish`.
|
1. Most of the tests are run in the '@stable' configuration and focus on chrome/ubuntu at a single resolution. This coverage is published to codecov with `npm run cov:e2e:stable:publish`.
|
||||||
@ -91,12 +91,14 @@ There are a few reasons that your GitHub PR could be failing beyond simple faile
|
|||||||
### Local=Pass and CI=Fail
|
### Local=Pass and CI=Fail
|
||||||
Although rare, it is possible that your test can pass locally but fail in CI.
|
Although rare, it is possible that your test can pass locally but fail in CI.
|
||||||
|
|
||||||
#### Busting Cache
|
### Reset your workspace
|
||||||
In certain circumstances, the CircleCI cache can become stale. In order to bust the cache, we've implemented a runtime boolean parameter in Circle CI creatively name BUST_CACHE. To execute:
|
It's possible that you're running with dependencies or a local environment which is out of sync with the branch you're working on. Make sure to execute the following:
|
||||||
1. Navigate to the branch in Circle CI believed to have stale cache.
|
|
||||||
1. Click on the 'Trigger Pipeline' button.
|
```sh
|
||||||
1. Add Parameter -> Parameter Type = boolean , Name = BUST_CACHE ,Value = true
|
nvm use
|
||||||
1. Click 'Trigger Pipeline'
|
npm run clean
|
||||||
|
npm install
|
||||||
|
```
|
||||||
|
|
||||||
#### Run tests in the same container as CI
|
#### Run tests in the same container as CI
|
||||||
|
|
||||||
|
7
e2e/.npmignore
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
*
|
||||||
|
!appActions.js
|
||||||
|
!baseFixtures.js
|
||||||
|
!pluginFixtures.js
|
||||||
|
!avpFixtures.js
|
||||||
|
!index.js
|
||||||
|
!*.md
|
@ -76,28 +76,30 @@ To read about how to write a good visual test, please see [How to write a great
|
|||||||
|
|
||||||
`npm run test:e2e:visual` commands will run all of the visual tests against a local instance of Open MCT. If no `PERCY_TOKEN` API key is found in the terminal or command line environment variables, no visual comparisons will be made.
|
`npm run test:e2e:visual` commands will run all of the visual tests against a local instance of Open MCT. If no `PERCY_TOKEN` API key is found in the terminal or command line environment variables, no visual comparisons will be made.
|
||||||
|
|
||||||
- `npm run test:e2e:visual:ci` will run against every commit and PR.
|
- `npm run test:e2e:visual:ci` will run against every commit and PR.
|
||||||
- `npm run test:e2e:visual:full` will run every night with additional comparisons made for Larger Displays and with the `snow` theme.
|
- `npm run test:e2e:visual:full` will run every night with additional comparisons made for Larger Displays and with the `snow` theme.
|
||||||
|
|
||||||
#### Percy.io
|
#### Percy.io
|
||||||
|
|
||||||
To make this possible, we're leveraging a 3rd party service, [Percy](https://percy.io/). This service maintains a copy of all changes, users, scm-metadata, and baselines to verify that the application looks and feels the same _unless approved by a Open MCT developer_. To request a Percy API token, please reach out to the Open MCT Dev team on GitHub. For more information, please see the official [Percy documentation](https://docs.percy.io/docs/visual-testing-basics).
|
To make this possible, we're leveraging a 3rd party service, [Percy](https://percy.io/). This service maintains a copy of all changes, users, scm-metadata, and baselines to verify that the application looks and feels the same _unless approved by a Open MCT developer_. To request a Percy API token, please reach out to the Open MCT Dev team on GitHub. For more information, please see the official [Percy documentation](https://docs.percy.io/docs/visual-testing-basics).
|
||||||
|
|
||||||
At present, we are using percy with two configuration files: `./e2e/.percy.nightly.yml` and `./e2e/.percy.ci.yml`. This is mainly to reduce the number of snapshots.
|
At present, we are using percy with two configuration files: `./e2e/.percy.nightly.yml` and `./e2e/.percy.ci.yml`. This is mainly to reduce the number of snapshots.
|
||||||
|
|
||||||
### Advanced: Snapshot Testing (Not Recommended)
|
### Advanced: Snapshot Testing (Not Recommended)
|
||||||
|
|
||||||
While snapshot testing offers a precise way to detect changes in your application without relying on third-party services like Percy.io, we've found that it doesn't offer any advantages over visual testing in our use-cases. Therefore, snapshot testing is **not recommended** for further implementation.
|
While snapshot testing offers a precise way to detect changes in your application without relying on third-party services like Percy.io, we've found that it doesn't offer any advantages over visual testing in our use-cases. Therefore, snapshot testing is **not recommended** for further implementation.
|
||||||
|
|
||||||
#### CI vs Manual Checks
|
#### CI vs Manual Checks
|
||||||
|
|
||||||
Snapshot tests can be reliably executed in Continuous Integration (CI) environments but lack the manual oversight provided by visual testing platforms like Percy.io. This means they may miss issues that a human reviewer could catch during manual checks.
|
Snapshot tests can be reliably executed in Continuous Integration (CI) environments but lack the manual oversight provided by visual testing platforms like Percy.io. This means they may miss issues that a human reviewer could catch during manual checks.
|
||||||
|
|
||||||
#### Example
|
#### Example
|
||||||
|
|
||||||
A single visual test assertion in Percy.io can be executed across 10 different browser and resolution combinations without additional setup, providing comprehensive testing with minimal configuration. In contrast, a snapshot test is restricted to a single OS and browser resolution, requiring more effort to achieve the same level of coverage.
|
A single visual test assertion in Percy.io can be executed across 10 different browser and resolution combinations without additional setup, providing comprehensive testing with minimal configuration. In contrast, a snapshot test is restricted to a single OS and browser resolution, requiring more effort to achieve the same level of coverage.
|
||||||
|
|
||||||
|
|
||||||
#### Further Reading
|
#### Further Reading
|
||||||
For those interested in the mechanics of snapshot testing with Playwright, you can refer to the [Playwright Snapshots Documentation](https://playwright.dev/docs/test-snapshots). However, keep in mind that we do not recommend using this approach.
|
|
||||||
|
|
||||||
|
For those interested in the mechanics of snapshot testing with Playwright, you can refer to the [Playwright Snapshots Documentation](https://playwright.dev/docs/test-snapshots). However, keep in mind that we do not recommend using this approach.
|
||||||
|
|
||||||
#### Open MCT's implementation
|
#### Open MCT's implementation
|
||||||
|
|
||||||
@ -118,14 +120,6 @@ When the `@snapshot` tests fail, they will need to be evaluated to determine if
|
|||||||
|
|
||||||
To compare a snapshot, run a test and open the html report with the 'Expected' vs 'Actual' screenshot. If the actual screenshot is preferred, then the source-controlled 'Expected' snapshots will need to be updated with the following scripts.
|
To compare a snapshot, run a test and open the html report with the 'Expected' vs 'Actual' screenshot. If the actual screenshot is preferred, then the source-controlled 'Expected' snapshots will need to be updated with the following scripts.
|
||||||
|
|
||||||
MacOS
|
|
||||||
|
|
||||||
```
|
|
||||||
npm run test:e2e:updatesnapshots
|
|
||||||
```
|
|
||||||
|
|
||||||
Linux/CI
|
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
// Replace {X.X.X} with the current Playwright version
|
// Replace {X.X.X} with the current Playwright version
|
||||||
// from our package.json or circleCI configuration file
|
// from our package.json or circleCI configuration file
|
||||||
@ -173,9 +167,9 @@ When an a11y test fails, the result must be interpreted in the html test report
|
|||||||
|
|
||||||
The open source performance tests function in three ways which match their naming and folder structure:
|
The open source performance tests function in three ways which match their naming and folder structure:
|
||||||
|
|
||||||
`./e2e/tests/performance` - The tests at the root of this folder path detect functional changes which are mostly apparent with large performance regressions like [this](https://github.com/nasa/openmct/issues/6879). These tests run against openmct webpack in `production-mode` with the `npm run test:perf:localhost` script.
|
`tests/performance` - The tests at the root of this folder path detect functional changes which are mostly apparent with large performance regressions like [this](https://github.com/nasa/openmct/issues/6879). These tests run against openmct webpack in `production-mode` with the `npm run test:perf:localhost` script.
|
||||||
`./e2e/tests/performance/contract/` - These tests serve as [contracts](https://martinfowler.com/bliki/ContractTest.html) for the locator logic, functionality, and assumptions will work in our downstream, closed source test suites. These tests run against openmct webpack in `dev-mode` with the `npm run test:perf:contract` script.
|
`tests/performance/contract/` - These tests serve as [contracts](https://martinfowler.com/bliki/ContractTest.html) for the locator logic, functionality, and assumptions will work in our downstream, closed source test suites. These tests run against openmct webpack in `dev-mode` with the `npm run test:perf:contract` script.
|
||||||
`./e2e/tests/performance/memory/` - These tests execute memory leak detection checks in various ways. This is expected to evolve as we move to the `memlab` project. These tests run against openmct webpack in `production-mode` with the `npm run test:perf:memory` script.
|
`tests/performance/memory/` - These tests execute memory leak detection checks in various ways. This is expected to evolve as we move to the `memlab` project. These tests run against openmct webpack in `production-mode` with the `npm run test:perf:memory` script.
|
||||||
|
|
||||||
These tests are expected to become blocking and gating with assertions as we extend the capabilities of Playwright.
|
These tests are expected to become blocking and gating with assertions as we extend the capabilities of Playwright.
|
||||||
|
|
||||||
@ -335,9 +329,11 @@ We have a Mission-need to support iPad and mobile devices. To run our test suite
|
|||||||
In general, our test suite is not designed to run against mobile devices as the mobile experience is a focused version of the application. Core functionality is missing (chiefly the 'Create' button). To bypass the object creation, we leverage the `storageState` properties for starting the mobile tests with localstorage.
|
In general, our test suite is not designed to run against mobile devices as the mobile experience is a focused version of the application. Core functionality is missing (chiefly the 'Create' button). To bypass the object creation, we leverage the `storageState` properties for starting the mobile tests with localstorage.
|
||||||
|
|
||||||
For now, the mobile tests will exist in the /tests/mobile/ suites and be executed with the
|
For now, the mobile tests will exist in the /tests/mobile/ suites and be executed with the
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
npm run test:e2e:mobile
|
npm run test:e2e:mobile
|
||||||
```
|
```
|
||||||
|
|
||||||
command.
|
command.
|
||||||
|
|
||||||
#### **Skipping or executing tests based on browser, os, and/os browser version:**
|
#### **Skipping or executing tests based on browser, os, and/os browser version:**
|
||||||
@ -377,6 +373,7 @@ In general, strive to test only through the UI as a user would. As stated in the
|
|||||||
By adhering to this principle, we can create tests that are both robust and reflective of actual user experiences.
|
By adhering to this principle, we can create tests that are both robust and reflective of actual user experiences.
|
||||||
|
|
||||||
#### How to make tests robust to function in other contexts (VISTA, COUCHDB, YAMCS, VIPER, etc.)
|
#### How to make tests robust to function in other contexts (VISTA, COUCHDB, YAMCS, VIPER, etc.)
|
||||||
|
|
||||||
1. Leverage the use of `appActions.js` methods such as `createDomainObjectWithDefaults()`. This ensures that your tests will create unique instances of objects for your test to interact with.
|
1. Leverage the use of `appActions.js` methods such as `createDomainObjectWithDefaults()`. This ensures that your tests will create unique instances of objects for your test to interact with.
|
||||||
1. Do not assert on the order or structure of objects available unless you created them yourself. These tests may be used against a persistent datastore like couchdb with many objects in the tree.
|
1. Do not assert on the order or structure of objects available unless you created them yourself. These tests may be used against a persistent datastore like couchdb with many objects in the tree.
|
||||||
1. Do not search for your created objects. Open MCT does not performance uniqueness checks so it's possible that your tests will break when run twice.
|
1. Do not search for your created objects. Open MCT does not performance uniqueness checks so it's possible that your tests will break when run twice.
|
||||||
@ -384,6 +381,7 @@ By adhering to this principle, we can create tests that are both robust and refl
|
|||||||
1. Leverage `await page.goto('./', { waitUntil: 'domcontentloaded' });` instead of `{ waitUntil: 'networkidle' }`. Tests run against deployments with websockets often have issues with the networkidle detection.
|
1. Leverage `await page.goto('./', { waitUntil: 'domcontentloaded' });` instead of `{ waitUntil: 'networkidle' }`. Tests run against deployments with websockets often have issues with the networkidle detection.
|
||||||
|
|
||||||
#### How to make tests faster and more resilient
|
#### How to make tests faster and more resilient
|
||||||
|
|
||||||
1. Avoid app interaction when possible. The best way of doing this is to navigate directly by URL:
|
1. Avoid app interaction when possible. The best way of doing this is to navigate directly by URL:
|
||||||
|
|
||||||
```js
|
```js
|
||||||
@ -396,10 +394,11 @@ By adhering to this principle, we can create tests that are both robust and refl
|
|||||||
|
|
||||||
1. Leverage `await page.goto('./', { waitUntil: 'domcontentloaded' });`
|
1. Leverage `await page.goto('./', { waitUntil: 'domcontentloaded' });`
|
||||||
- Initial navigation should _almost_ always use the `{ waitUntil: 'domcontentloaded' }` option.
|
- Initial navigation should _almost_ always use the `{ waitUntil: 'domcontentloaded' }` option.
|
||||||
1. Avoid repeated setup to test a single assertion. Write longer tests with multiple soft assertions.
|
1. Avoid repeated setup to test a single assertion. Write longer tests with multiple soft assertions.
|
||||||
This ensures that your changes will be picked up with large refactors.
|
This ensures that your changes will be picked up with large refactors.
|
||||||
|
|
||||||
##### Utilizing LocalStorage
|
##### Utilizing LocalStorage
|
||||||
|
|
||||||
1. In order to save test runtime in the case of tests that require a decent amount of initial setup (such as in the case of testing complex displays), you may use [Playwright's `storageState` feature](https://playwright.dev/docs/api/class-browsercontext#browser-context-storage-state) to generate and load localStorage states.
|
1. In order to save test runtime in the case of tests that require a decent amount of initial setup (such as in the case of testing complex displays), you may use [Playwright's `storageState` feature](https://playwright.dev/docs/api/class-browsercontext#browser-context-storage-state) to generate and load localStorage states.
|
||||||
1. To generate a localStorage state to be used in a test:
|
1. To generate a localStorage state to be used in a test:
|
||||||
- Add an e2e test to our generateLocalStorageData suite which sets the initial state (creating/configuring objects, etc.), saving it in the `test-data` folder:
|
- Add an e2e test to our generateLocalStorageData suite which sets the initial state (creating/configuring objects, etc.), saving it in the `test-data` folder:
|
||||||
@ -420,7 +419,6 @@ By adhering to this principle, we can create tests that are both robust and refl
|
|||||||
});
|
});
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
||||||
### How to write a great test
|
### How to write a great test
|
||||||
|
|
||||||
- Avoid using css locators to find elements to the page. Use modern web accessible locators like `getByRole`
|
- Avoid using css locators to find elements to the page. Use modern web accessible locators like `getByRole`
|
||||||
@ -436,7 +434,7 @@ By adhering to this principle, we can create tests that are both robust and refl
|
|||||||
await notesInput.fill(testNotes);
|
await notesInput.fill(testNotes);
|
||||||
```
|
```
|
||||||
|
|
||||||
#### How to Write a Great Visual Test
|
#### How to Write a Great Visual Test
|
||||||
|
|
||||||
1. **Look for the Unknown Unknowns**: Avoid asserting on specific differences in the visual diff. Visual tests are most effective for identifying unknown unknowns.
|
1. **Look for the Unknown Unknowns**: Avoid asserting on specific differences in the visual diff. Visual tests are most effective for identifying unknown unknowns.
|
||||||
|
|
||||||
@ -445,23 +443,27 @@ By adhering to this principle, we can create tests that are both robust and refl
|
|||||||
3. **Expect the Unexpected**: Use functional expect statements only to verify assumptions about the state between steps. A great visual test doesn't fail during the test itself, but rather when changes are reviewed in Percy.io.
|
3. **Expect the Unexpected**: Use functional expect statements only to verify assumptions about the state between steps. A great visual test doesn't fail during the test itself, but rather when changes are reviewed in Percy.io.
|
||||||
|
|
||||||
4. **Control Variability**: Account for variations inherent in working with time-based telemetry and clocks.
|
4. **Control Variability**: Account for variations inherent in working with time-based telemetry and clocks.
|
||||||
- Utilize `percyCSS` to ignore time-based elements. For more details, consult our [percyCSS file](./.percy.ci.yml).
|
|
||||||
- Use Open MCT's fixed-time mode unless explicitly testing realtime clock
|
- Utilize `percyCSS` to ignore time-based elements. For more details, consult our [percyCSS file](./.percy.ci.yml).
|
||||||
- Employ the `createExampleTelemetryObject` appAction to source telemetry and specify a `name` to avoid autogenerated names.
|
- Use Open MCT's fixed-time mode unless explicitly testing realtime clock
|
||||||
- Avoid creating objects with a time component like timers and clocks.
|
- Employ the `createExampleTelemetryObject` appAction to source telemetry and specify a `name` to avoid autogenerated names.
|
||||||
|
- Avoid creating objects with a time component like timers and clocks.
|
||||||
|
|
||||||
5. **Hide the Tree and Inspector**: Generally, your test will not require comparisons involving the tree and inspector. These aspects are covered in component-specific tests (explained below). To exclude them from the comparison by default, navigate to the root of the main view with the tree and inspector hidden:
|
5. **Hide the Tree and Inspector**: Generally, your test will not require comparisons involving the tree and inspector. These aspects are covered in component-specific tests (explained below). To exclude them from the comparison by default, navigate to the root of the main view with the tree and inspector hidden:
|
||||||
- `await page.goto('./#/browse/mine?hideTree=true&hideInspector=true')`
|
- `await page.goto('./#/browse/mine?hideTree=true&hideInspector=true')`
|
||||||
|
|
||||||
6. **Component-Specific Tests**: If you wish to focus on a particular component, use the `/visual-a11y/component/` folder and limit the scope of the comparison to that component. For instance:
|
6. **Component-Specific Tests**: If you wish to focus on a particular component, use the `/visual-a11y/component/` folder and limit the scope of the comparison to that component. For instance:
|
||||||
|
|
||||||
```js
|
```js
|
||||||
await percySnapshot(page, `Tree Pane w/ single level expanded (theme: ${theme})`, {
|
await percySnapshot(page, `Tree Pane w/ single level expanded (theme: ${theme})`, {
|
||||||
scope: treePane
|
scope: treePane
|
||||||
});
|
});
|
||||||
```
|
```
|
||||||
|
|
||||||
- Note: The `scope` variable can be any valid CSS selector.
|
- Note: The `scope` variable can be any valid CSS selector.
|
||||||
|
|
||||||
7. **Write many `percySnapshot` commands in a single test**: In line with our approach to longer functional tests, we recommend that many test percySnapshots are taken in a single test. For instance:
|
7. **Write many `percySnapshot` commands in a single test**: In line with our approach to longer functional tests, we recommend that many test percySnapshots are taken in a single test. For instance:
|
||||||
|
|
||||||
```js
|
```js
|
||||||
//<Some interesting state>
|
//<Some interesting state>
|
||||||
await percySnapshot(page, `Before object expanded (theme: ${theme})`);
|
await percySnapshot(page, `Before object expanded (theme: ${theme})`);
|
||||||
@ -511,11 +513,35 @@ test.describe('foo test suite', () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
```
|
```
|
||||||
|
|
||||||
More info and options for `overrideClock` can be found in [baseFixtures.js](baseFixtures.js)
|
More info and options for `overrideClock` can be found in [baseFixtures.js](baseFixtures.js)
|
||||||
|
|
||||||
- Working with multiple pages
|
- Working with multiple pages
|
||||||
There are instances where multiple browser pages will needed to verify multi-page or multi-tab application behavior. Make sure to use the `@2p` annotation as well as name each page appropriately: i.e. `page1` and `page2` or `tab1` and `tab2` depending on the intended use case. Generally pages should be used unless testing `sharedWorker` code, specifically.
|
There are instances where multiple browser pages will needed to verify multi-page or multi-tab application behavior. Make sure to use the `@2p` annotation as well as name each page appropriately: i.e. `page1` and `page2` or `tab1` and `tab2` depending on the intended use case. Generally pages should be used unless testing `sharedWorker` code, specifically.
|
||||||
|
|
||||||
|
- Working with file downloads and JSON data
|
||||||
|
Open MCT has the capability of exporting certain objects in the form of a JSON file handled by the chrome browser. The best example of this type of test can be found in the exportAsJson test.
|
||||||
|
|
||||||
|
```js
|
||||||
|
const [download] = await Promise.all([
|
||||||
|
page.waitForEvent('download'), // Waits for the download event
|
||||||
|
page.getByLabel('Export as JSON').click() // Triggers the download
|
||||||
|
]);
|
||||||
|
|
||||||
|
// Wait for the download process to complete
|
||||||
|
const path = await download.path();
|
||||||
|
|
||||||
|
// Read the contents of the downloaded file using readFile from fs/promises
|
||||||
|
const fileContents = await fs.readFile(path, 'utf8');
|
||||||
|
const jsonData = JSON.parse(fileContents);
|
||||||
|
|
||||||
|
// Use the function to retrieve the key
|
||||||
|
const key = getFirstKeyFromOpenMctJson(jsonData);
|
||||||
|
|
||||||
|
// Verify the contents of the JSON file
|
||||||
|
expect(jsonData.openmct[key]).toHaveProperty('name', 'e2e folder');
|
||||||
|
```
|
||||||
|
|
||||||
### Reporting
|
### Reporting
|
||||||
|
|
||||||
Test Reporting is done through official Playwright reporters and the CI Systems which execute them.
|
Test Reporting is done through official Playwright reporters and the CI Systems which execute them.
|
||||||
@ -591,6 +617,7 @@ A single e2e test in Open MCT is extended to run:
|
|||||||
### Writing Tests
|
### Writing Tests
|
||||||
|
|
||||||
Playwright provides 3 supported methods of debugging and authoring tests:
|
Playwright provides 3 supported methods of debugging and authoring tests:
|
||||||
|
|
||||||
- A 'watch mode' for running tests locally and debugging on the fly
|
- A 'watch mode' for running tests locally and debugging on the fly
|
||||||
- A 'debug mode' for debugging tests and writing assertions against tests
|
- A 'debug mode' for debugging tests and writing assertions against tests
|
||||||
- A 'VSCode plugin' for debugging tests within the VSCode IDE.
|
- A 'VSCode plugin' for debugging tests within the VSCode IDE.
|
||||||
|
@ -275,6 +275,17 @@ async function navigateToObjectWithFixedTimeBounds(page, url, start, end) {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Navigates directly to a given object url, in real-time mode.
|
||||||
|
* @param {import('@playwright/test').Page} page
|
||||||
|
* @param {string} url The url to the domainObject
|
||||||
|
*/
|
||||||
|
async function navigateToObjectWithRealTime(page, url, start = '1800000', end = '30000') {
|
||||||
|
await page.goto(
|
||||||
|
`${url}?tc.mode=local&tc.startDelta=${start}&tc.endDelta=${end}&tc.timeSystem=utc`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 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.
|
||||||
@ -392,6 +403,8 @@ async function setTimeConductorMode(page, isFixedTimespan = true) {
|
|||||||
await page.getByRole('menuitem', { name: /Real-Time/ }).click();
|
await page.getByRole('menuitem', { name: /Real-Time/ }).click();
|
||||||
await page.waitForURL(/tc\.mode=local/);
|
await page.waitForURL(/tc\.mode=local/);
|
||||||
}
|
}
|
||||||
|
//dismiss the time conductor popup
|
||||||
|
await page.getByLabel('Discard changes and close time popup').click();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -505,15 +518,14 @@ async function setTimeConductorBounds(page, startDate, endDate) {
|
|||||||
* @param {string} startDate
|
* @param {string} startDate
|
||||||
* @param {string} endDate
|
* @param {string} endDate
|
||||||
*/
|
*/
|
||||||
async function setIndependentTimeConductorBounds(page, startDate, endDate) {
|
async function setIndependentTimeConductorBounds(page, { start, end }) {
|
||||||
// Activate Independent Time Conductor in Fixed Time Mode
|
// Activate Independent Time Conductor
|
||||||
await page.getByRole('switch').click();
|
await page.getByLabel('Enable Independent Time Conductor').click();
|
||||||
|
|
||||||
// Bring up the time conductor popup
|
// Bring up the time conductor popup
|
||||||
await page.click('.c-conductor-holder--compact .c-compact-tc');
|
await page.getByLabel('Independent Time Conductor Settings').click();
|
||||||
await expect(page.locator('.itc-popout')).toBeInViewport();
|
await expect(page.locator('.itc-popout')).toBeInViewport();
|
||||||
|
await setTimeBounds(page, start, end);
|
||||||
await setTimeBounds(page, startDate, endDate);
|
|
||||||
|
|
||||||
await page.keyboard.press('Enter');
|
await page.keyboard.press('Enter');
|
||||||
}
|
}
|
||||||
@ -655,6 +667,7 @@ export {
|
|||||||
getFocusedObjectUuid,
|
getFocusedObjectUuid,
|
||||||
getHashUrlToDomainObject,
|
getHashUrlToDomainObject,
|
||||||
navigateToObjectWithFixedTimeBounds,
|
navigateToObjectWithFixedTimeBounds,
|
||||||
|
navigateToObjectWithRealTime,
|
||||||
openObjectTreeContextMenu,
|
openObjectTreeContextMenu,
|
||||||
renameObjectFromContextMenu,
|
renameObjectFromContextMenu,
|
||||||
setEndOffset,
|
setEndOffset,
|
||||||
@ -663,5 +676,6 @@ export {
|
|||||||
setRealTimeMode,
|
setRealTimeMode,
|
||||||
setStartOffset,
|
setStartOffset,
|
||||||
setTimeConductorBounds,
|
setTimeConductorBounds,
|
||||||
|
setTimeConductorMode,
|
||||||
waitForPlotsToRender
|
waitForPlotsToRender
|
||||||
};
|
};
|
||||||
|
@ -36,27 +36,67 @@
|
|||||||
import AxeBuilder from '@axe-core/playwright';
|
import AxeBuilder from '@axe-core/playwright';
|
||||||
import fs from 'fs';
|
import fs from 'fs';
|
||||||
import path from 'path';
|
import path from 'path';
|
||||||
|
import { fileURLToPath } from 'url';
|
||||||
|
|
||||||
import { expect, test } from './pluginFixtures.js';
|
import { expect, test } from './pluginFixtures.js';
|
||||||
|
|
||||||
// Constants for repeated values
|
// Constants for repeated values
|
||||||
const TEST_RESULTS_DIR = './test-results';
|
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
||||||
|
const TEST_RESULTS_DIR = path.join(__dirname, './test-results');
|
||||||
|
|
||||||
|
const extendedTest = test.extend({
|
||||||
|
/**
|
||||||
|
* Overrides the default screenshot function to apply default options that should apply to all
|
||||||
|
* screenshots taken in the AVP tests.
|
||||||
|
*
|
||||||
|
* @param {import('@playwright/test').PlaywrightTestArgs} args - The Playwright test arguments.
|
||||||
|
* @param {Function} use - The function to use the page object.
|
||||||
|
* Defaults:
|
||||||
|
* - Disables animations
|
||||||
|
* - Masks the clock indicator
|
||||||
|
* - Masks the time conductor last update time in realtime mode
|
||||||
|
* - Masks the time conductor start bounds in fixed mode
|
||||||
|
* - Masks the time conductor end bounds in fixed mode
|
||||||
|
*/
|
||||||
|
page: async ({ page }, use) => {
|
||||||
|
const playwrightScreenshot = page.screenshot;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Override the screenshot function to always mask a given set of locators which will always
|
||||||
|
* show variance across screenshots. Defaults may be overridden by passing in options to the
|
||||||
|
* screenshot function.
|
||||||
|
* @param {import('@playwright/test').PageScreenshotOptions} options - The options for the screenshot.
|
||||||
|
* @returns {Promise<Buffer>} Returns the screenshot as a buffer.
|
||||||
|
*/
|
||||||
|
page.screenshot = async function (options = {}) {
|
||||||
|
const mask = [
|
||||||
|
this.getByLabel('Clock Indicator'), // Mask the clock indicator
|
||||||
|
this.getByLabel('Last update'), // Mask the time conductor last update time in realtime mode
|
||||||
|
this.getByLabel('Start bounds'), // Mask the time conductor start bounds in fixed mode
|
||||||
|
this.getByLabel('End bounds') // Mask the time conductor end bounds in fixed mode
|
||||||
|
];
|
||||||
|
|
||||||
|
const result = await playwrightScreenshot.call(this, {
|
||||||
|
animations: 'disabled',
|
||||||
|
mask,
|
||||||
|
...options // Pass through or override any options
|
||||||
|
});
|
||||||
|
return result;
|
||||||
|
};
|
||||||
|
|
||||||
|
await use(page);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Scans for accessibility violations on a page and writes a report to disk if violations are found.
|
* Scans for accessibility violations on a page and writes a report to disk if violations are found.
|
||||||
* Automatically asserts that no violations should be present.
|
* Automatically asserts that no violations should be present.
|
||||||
*
|
*
|
||||||
* @typedef {object} GenerateReportOptions
|
|
||||||
* @property {string} [reportName] - The name for the report file.
|
|
||||||
*
|
|
||||||
* @param {import('playwright').Page} page - The page object from Playwright.
|
* @param {import('playwright').Page} page - The page object from Playwright.
|
||||||
* @param {string} testCaseName - The name of the test case.
|
* @param {string} testCaseName - The name of the test case.
|
||||||
* @param {GenerateReportOptions} [options={}] - The options for the report generation.
|
* @param {{ reportName?: string }} [options={}] - The options for the report generation.
|
||||||
*
|
* @returns {Promise<Object|null>} Returns the accessibility scan results if violations are found, otherwise returns null.
|
||||||
* @returns {Promise<object|null>} Returns the accessibility scan results if violations are found,
|
|
||||||
* otherwise returns null.
|
|
||||||
*/
|
*/
|
||||||
/* eslint-disable no-undef */
|
|
||||||
export async function scanForA11yViolations(page, testCaseName, options = {}) {
|
export async function scanForA11yViolations(page, testCaseName, options = {}) {
|
||||||
const builder = new AxeBuilder({ page });
|
const builder = new AxeBuilder({ page });
|
||||||
builder.withTags(['wcag2aa']);
|
builder.withTags(['wcag2aa']);
|
||||||
@ -93,4 +133,4 @@ export async function scanForA11yViolations(page, testCaseName, options = {}) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export { expect, test };
|
export { expect, extendedTest as test };
|
||||||
|
@ -1,4 +1,3 @@
|
|||||||
/* eslint-disable no-undef */
|
|
||||||
/*****************************************************************************
|
/*****************************************************************************
|
||||||
* Open MCT, Copyright (c) 2014-2024, United States Government
|
* Open MCT, Copyright (c) 2014-2024, United States Government
|
||||||
* as represented by the Administrator of the National Aeronautics and Space
|
* as represented by the Administrator of the National Aeronautics and Space
|
||||||
@ -40,7 +39,7 @@ import { v4 as uuid } from 'uuid';
|
|||||||
* @see {@link https://github.com/microsoft/playwright/discussions/11690 Github Discussion}
|
* @see {@link https://github.com/microsoft/playwright/discussions/11690 Github Discussion}
|
||||||
* @private
|
* @private
|
||||||
* @param {import('@playwright/test').ConsoleMessage} msg
|
* @param {import('@playwright/test').ConsoleMessage} msg
|
||||||
* @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();
|
||||||
@ -61,14 +60,16 @@ function waitForAnimations(locator) {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
const istanbulCLIOutput = fileURLToPath(new URL('.nyc_output', import.meta.url));
|
||||||
* This is part of our codecoverage shim.
|
|
||||||
* @see {@link https://github.com/mxschmitt/playwright-test-coverage Github Example Project}
|
|
||||||
* @constant {string}
|
|
||||||
*/
|
|
||||||
const istanbulCLIOutput = path.join(process.cwd(), '.nyc_output');
|
|
||||||
|
|
||||||
const extendedTest = test.extend({
|
const extendedTest = test.extend({
|
||||||
|
/**
|
||||||
|
* Path to output raw coverage files. Can be overridden in Playwright config file.
|
||||||
|
* @see {@link https://github.com/mxschmitt/playwright-test-coverage Github Example Project}
|
||||||
|
* @constant {string}
|
||||||
|
*/
|
||||||
|
|
||||||
|
coveragePath: [istanbulCLIOutput, { option: true }],
|
||||||
/**
|
/**
|
||||||
* 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.
|
||||||
@ -111,21 +112,55 @@ const extendedTest = test.extend({
|
|||||||
scope: 'test'
|
scope: 'test'
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
/**
|
||||||
|
* Exposes a function to manually tick the clock. This is useful when overriding the clock to not
|
||||||
|
* tick (`shouldAdvanceTime: false`) for visual tests, as events such as re-renders and router params
|
||||||
|
* updates are clock-driven and must be manually ticked.
|
||||||
|
*
|
||||||
|
* Usage:
|
||||||
|
* ```js
|
||||||
|
* test.describe('Manual Clock Tick', () => {
|
||||||
|
* test.use({
|
||||||
|
* clockOptions: {
|
||||||
|
* now: MISSION_TIME, // Set to the desired time
|
||||||
|
* shouldAdvanceTime: false // Clock overridden to no longer tick
|
||||||
|
* }
|
||||||
|
* });
|
||||||
|
* test('Visual - Manual Clock Tick', async ({ page, tick }) => {
|
||||||
|
* // Tick the clock 2 seconds in the future
|
||||||
|
* await tick(2000);
|
||||||
|
* });
|
||||||
|
* });
|
||||||
|
* ```
|
||||||
|
*
|
||||||
|
* @param {Object} param0
|
||||||
|
* @param {import('@playwright/test').Page} param0.page
|
||||||
|
* @param {import('@playwright/test').Use} param0.use
|
||||||
|
*/
|
||||||
|
tick: async ({ page }, use) => {
|
||||||
|
// eslint-disable-next-line func-style
|
||||||
|
const tick = async (milliseconds) => {
|
||||||
|
await page.evaluate((_milliseconds) => {
|
||||||
|
window.__clock.tick(_milliseconds);
|
||||||
|
}, milliseconds);
|
||||||
|
};
|
||||||
|
await use(tick);
|
||||||
|
},
|
||||||
/**
|
/**
|
||||||
* Extends the base context class to add codecoverage shim.
|
* Extends the base context class to add codecoverage shim.
|
||||||
* @see {@link https://github.com/mxschmitt/playwright-test-coverage Github Project}
|
* @see {@link https://github.com/mxschmitt/playwright-test-coverage Github Project}
|
||||||
*/
|
*/
|
||||||
context: async ({ context }, use) => {
|
context: async ({ context, coveragePath }, use) => {
|
||||||
await context.addInitScript(() =>
|
await context.addInitScript(() =>
|
||||||
window.addEventListener('beforeunload', () =>
|
window.addEventListener('beforeunload', () =>
|
||||||
window.collectIstanbulCoverage(JSON.stringify(window.__coverage__))
|
window.collectIstanbulCoverage(JSON.stringify(window.__coverage__))
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
await fs.promises.mkdir(istanbulCLIOutput, { recursive: true });
|
await fs.promises.mkdir(coveragePath, { recursive: true });
|
||||||
await context.exposeFunction('collectIstanbulCoverage', (coverageJSON) => {
|
await context.exposeFunction('collectIstanbulCoverage', (coverageJSON) => {
|
||||||
if (coverageJSON) {
|
if (coverageJSON) {
|
||||||
fs.writeFileSync(
|
fs.writeFileSync(
|
||||||
path.join(istanbulCLIOutput, `playwright_coverage_${uuid()}.json`),
|
path.join(coveragePath, `playwright_coverage_${uuid()}.json`),
|
||||||
coverageJSON
|
coverageJSON
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -133,9 +168,9 @@ const extendedTest = test.extend({
|
|||||||
|
|
||||||
await use(context);
|
await use(context);
|
||||||
for (const page of context.pages()) {
|
for (const page of context.pages()) {
|
||||||
await page.evaluate(() =>
|
await page.evaluate(() => {
|
||||||
window.collectIstanbulCoverage(JSON.stringify(window.__coverage__))
|
window.collectIstanbulCoverage(JSON.stringify(window.__coverage__));
|
||||||
);
|
});
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
/**
|
/**
|
||||||
@ -154,17 +189,13 @@ const extendedTest = test.extend({
|
|||||||
// function in the generatorWorker context. This is necessary
|
// function in the generatorWorker context. This is necessary
|
||||||
// to ensure that example telemetry data is generated for the new clock time.
|
// to ensure that example telemetry data is generated for the new clock time.
|
||||||
if (clockOptions?.now !== undefined) {
|
if (clockOptions?.now !== undefined) {
|
||||||
page.on(
|
page.on('worker', (worker) => {
|
||||||
'worker',
|
if (worker.url().includes('generatorWorker')) {
|
||||||
(worker) => {
|
worker.evaluate((time) => {
|
||||||
if (worker.url().includes('generatorWorker')) {
|
self.Date.now = () => time;
|
||||||
worker.evaluate((time) => {
|
}, clockOptions.now);
|
||||||
self.Date.now = () => time;
|
}
|
||||||
});
|
});
|
||||||
}
|
|
||||||
},
|
|
||||||
clockOptions.now
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Capture any console errors during test execution
|
// Capture any console errors during test execution
|
||||||
|
@ -1,4 +1,3 @@
|
|||||||
/* eslint-disable prettier/prettier */
|
|
||||||
/**
|
/**
|
||||||
* Constants which may be used across all e2e tests.
|
* Constants which may be used across all e2e tests.
|
||||||
*/
|
*/
|
||||||
@ -8,12 +7,30 @@
|
|||||||
* - Used for overriding the browser clock in tests.
|
* - Used for overriding the browser clock in tests.
|
||||||
*/
|
*/
|
||||||
export const MISSION_TIME = 1732413600000; // Saturday, November 23, 2024 6:00:00 PM GMT-08:00 (Thanksgiving Dinner Time)
|
export const MISSION_TIME = 1732413600000; // Saturday, November 23, 2024 6:00:00 PM GMT-08:00 (Thanksgiving Dinner Time)
|
||||||
|
// Subtracting 30 minutes from MISSION_TIME
|
||||||
|
export const MISSION_TIME_FIXED_START = 1732413600000 - 1800000; // 1732411800000
|
||||||
|
|
||||||
|
// Adding 1 minute to MISSION_TIME
|
||||||
|
export const MISSION_TIME_FIXED_END = 1732413600000 + 60000; // 1732413660000
|
||||||
/**
|
/**
|
||||||
* URL Constants
|
* URL Constants
|
||||||
* - This is the URL that the browser will be directed to when running visual tests. This URL
|
* These constants are used for initial navigation in visual tests, in either fixed or realtime mode.
|
||||||
* - hides the tree and inspector to prevent visual noise
|
* They navigate to the 'My Items' folder at MISSION_TIME.
|
||||||
* - sets the time bounds to a fixed range
|
* They set the following url parameters:
|
||||||
|
* - tc.mode - The time conductor mode ('fixed' or 'local')
|
||||||
|
* - tc.startBound - The time conductor start bound (when in fixed mode)
|
||||||
|
* - tc.endBound - The time conductor end bound (when in fixed mode)
|
||||||
|
* - tc.startDelta - The time conductor start delta (when in realtime mode)
|
||||||
|
* - tc.endDelta - The time conductor end delta (when in realtime mode)
|
||||||
|
* - tc.timeSystem - The time conductor time system ('utc')
|
||||||
|
* - view - The view to display ('grid')
|
||||||
|
* - hideInspector - Whether to hide the inspector (true)
|
||||||
|
* - hideTree - Whether to hide the tree (true)
|
||||||
|
* @typedef {string} VisualUrl
|
||||||
*/
|
*/
|
||||||
export const VISUAL_URL =
|
|
||||||
'./#/browse/mine?tc.mode=fixed&tc.startBound=1693592063607&tc.endBound=1693593893607&tc.timeSystem=utc&view=grid&hideInspector=true&hideTree=true';
|
/** @type {VisualUrl} */
|
||||||
|
export const VISUAL_FIXED_URL = `./#/browse/mine?tc.mode=fixed&tc.startBound=${MISSION_TIME_FIXED_START}&tc.endBound=${MISSION_TIME_FIXED_END}&tc.timeSystem=utc&view=grid&hideInspector=true&hideTree=true`;
|
||||||
|
/** @type {VisualUrl} */
|
||||||
|
export const VISUAL_REALTIME_URL =
|
||||||
|
'./#/browse/mine?tc.mode=local&tc.timeSystem=utc&view=grid&tc.startDelta=1800000&tc.endDelta=30000&hideTree=true&hideInspector=true';
|
||||||
|
47
e2e/helper/hotkeys/clipboard.js
Normal file
@ -0,0 +1,47 @@
|
|||||||
|
/*****************************************************************************
|
||||||
|
* Open MCT, Copyright (c) 2014-2024, United States Government
|
||||||
|
* as represented by the Administrator of the National Aeronautics and Space
|
||||||
|
* Administration. All rights reserved.
|
||||||
|
*
|
||||||
|
* Open MCT is licensed under the Apache License, Version 2.0 (the
|
||||||
|
* "License"); you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0.
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
* License for the specific language governing permissions and limitations
|
||||||
|
* under the License.
|
||||||
|
*
|
||||||
|
* Open MCT includes source code licensed under additional open source
|
||||||
|
* licenses. See the Open Source Licenses file (LICENSES.md) included with
|
||||||
|
* this source code distribution or the Licensing information page available
|
||||||
|
* at runtime from the About dialog for additional information.
|
||||||
|
*****************************************************************************/
|
||||||
|
|
||||||
|
const isMac = process.platform === 'darwin';
|
||||||
|
const modifier = isMac ? 'Meta' : 'Control';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {import('@playwright/test').Page} page
|
||||||
|
*/
|
||||||
|
async function selectAll(page) {
|
||||||
|
await page.keyboard.press(`${modifier}+KeyA`);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {import('@playwright/test').Page} page
|
||||||
|
*/
|
||||||
|
async function copy(page) {
|
||||||
|
await page.keyboard.press(`${modifier}+KeyC`);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {import('@playwright/test').Page} page
|
||||||
|
*/
|
||||||
|
async function paste(page) {
|
||||||
|
await page.keyboard.press(`${modifier}+KeyV`);
|
||||||
|
}
|
||||||
|
|
||||||
|
export { copy, paste, selectAll };
|
23
e2e/helper/hotkeys/hotkeys.js
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
/*****************************************************************************
|
||||||
|
* Open MCT, Copyright (c) 2014-2024, United States Government
|
||||||
|
* as represented by the Administrator of the National Aeronautics and Space
|
||||||
|
* Administration. All rights reserved.
|
||||||
|
*
|
||||||
|
* Open MCT is licensed under the Apache License, Version 2.0 (the
|
||||||
|
* "License"); you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0.
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
* License for the specific language governing permissions and limitations
|
||||||
|
* under the License.
|
||||||
|
*
|
||||||
|
* Open MCT includes source code licensed under additional open source
|
||||||
|
* licenses. See the Open Source Licenses file (LICENSES.md) included with
|
||||||
|
* this source code distribution or the Licensing information page available
|
||||||
|
* at runtime from the About dialog for additional information.
|
||||||
|
*****************************************************************************/
|
||||||
|
|
||||||
|
export * from './clipboard.js';
|
@ -28,16 +28,28 @@ import { fileURLToPath } from 'url';
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {import('@playwright/test').Page} page
|
* @param {import('@playwright/test').Page} page
|
||||||
|
* @param {string} text
|
||||||
*/
|
*/
|
||||||
async function enterTextEntry(page, text) {
|
async function enterTextEntry(page, text) {
|
||||||
// Click the 'Add Notebook Entry' area
|
await addNotebookEntry(page);
|
||||||
await page.locator(NOTEBOOK_DROP_AREA).click();
|
await enterTextInLastEntry(page, text);
|
||||||
|
|
||||||
// enter text
|
|
||||||
await page.getByLabel('Notebook Entry Input').last().fill(text);
|
|
||||||
await commitEntry(page);
|
await commitEntry(page);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {import('@playwright/test').Page} page
|
||||||
|
*/
|
||||||
|
async function addNotebookEntry(page) {
|
||||||
|
await page.locator(NOTEBOOK_DROP_AREA).click();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {import('@playwright/test').Page} page
|
||||||
|
*/
|
||||||
|
async function enterTextInLastEntry(page, text) {
|
||||||
|
await page.getByLabel('Notebook Entry Input').last().fill(text);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {import('@playwright/test').Page} page
|
* @param {import('@playwright/test').Page} page
|
||||||
*/
|
*/
|
||||||
@ -68,7 +80,6 @@ async function commitEntry(page) {
|
|||||||
* @param {import('@playwright/test').Page} page
|
* @param {import('@playwright/test').Page} page
|
||||||
*/
|
*/
|
||||||
async function startAndAddRestrictedNotebookObject(page) {
|
async function startAndAddRestrictedNotebookObject(page) {
|
||||||
// eslint-disable-next-line no-undef
|
|
||||||
await page.addInitScript({
|
await page.addInitScript({
|
||||||
path: fileURLToPath(new URL('./addInitRestrictedNotebook.js', import.meta.url))
|
path: fileURLToPath(new URL('./addInitRestrictedNotebook.js', import.meta.url))
|
||||||
});
|
});
|
||||||
@ -141,10 +152,13 @@ async function createNotebookEntryAndTags(page, iterations = 1) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export {
|
export {
|
||||||
|
addNotebookEntry,
|
||||||
|
commitEntry,
|
||||||
createNotebookAndEntry,
|
createNotebookAndEntry,
|
||||||
createNotebookEntryAndTags,
|
createNotebookEntryAndTags,
|
||||||
dragAndDropEmbed,
|
dragAndDropEmbed,
|
||||||
enterTextEntry,
|
enterTextEntry,
|
||||||
|
enterTextInLastEntry,
|
||||||
lockPage,
|
lockPage,
|
||||||
startAndAddRestrictedNotebookObject
|
startAndAddRestrictedNotebookObject
|
||||||
};
|
};
|
||||||
|
@ -29,7 +29,7 @@ import { expect } from '../pluginFixtures.js';
|
|||||||
* for each activity in the plan data per group, using the earliest activity's
|
* for each activity in the plan data per group, using the earliest activity's
|
||||||
* start time as the start bound and the current activity's end time as the end bound.
|
* start time as the start bound and the current activity's end time as the end bound.
|
||||||
* @param {import('@playwright/test').Page} page the page
|
* @param {import('@playwright/test').Page} page the page
|
||||||
* @param {object} plan The raw plan json to assert against
|
* @param {Object} plan The raw plan json to assert against
|
||||||
* @param {string} objectUrl The URL of the object to assert against (plan or gantt chart)
|
* @param {string} 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) {
|
||||||
@ -86,7 +86,7 @@ function activitiesWithinTimeBounds(start1, end1, start2, end2) {
|
|||||||
* Asserts that the swim lanes / groups in the plan view matches the order of
|
* Asserts that the swim lanes / groups in the plan view matches the order of
|
||||||
* groups in the plan data.
|
* groups in the plan data.
|
||||||
* @param {import('@playwright/test').Page} page the page
|
* @param {import('@playwright/test').Page} page the page
|
||||||
* @param {object} plan The raw plan json to assert against
|
* @param {Object} plan The raw plan json to assert against
|
||||||
* @param {string} objectUrl The URL of the object to assert against (plan or gantt chart)
|
* @param {string} objectUrl The URL of the object to assert against (plan or gantt chart)
|
||||||
*/
|
*/
|
||||||
export async function assertPlanOrderedSwimLanes(page, plan, objectUrl) {
|
export async function assertPlanOrderedSwimLanes(page, plan, objectUrl) {
|
||||||
@ -110,7 +110,7 @@ export async function assertPlanOrderedSwimLanes(page, plan, objectUrl) {
|
|||||||
* Navigate to the plan view, switch to fixed time mode,
|
* Navigate to the plan view, switch to fixed time mode,
|
||||||
* and set the bounds to span all activities.
|
* and set the bounds to span all activities.
|
||||||
* @param {import('@playwright/test').Page} page
|
* @param {import('@playwright/test').Page} page
|
||||||
* @param {object} planJson
|
* @param {Object} planJson
|
||||||
* @param {string} planObjectUrl
|
* @param {string} planObjectUrl
|
||||||
*/
|
*/
|
||||||
export async function setBoundsToSpanAllActivities(page, planJson, planObjectUrl) {
|
export async function setBoundsToSpanAllActivities(page, planJson, planObjectUrl) {
|
||||||
@ -125,7 +125,7 @@ export async function setBoundsToSpanAllActivities(page, planJson, planObjectUrl
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {object} planJson
|
* @param {Object} planJson
|
||||||
* @returns {number}
|
* @returns {number}
|
||||||
*/
|
*/
|
||||||
export function getEarliestStartTime(planJson) {
|
export function getEarliestStartTime(planJson) {
|
||||||
@ -135,7 +135,7 @@ export function getEarliestStartTime(planJson) {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* @param {object} planJson
|
* @param {Object} planJson
|
||||||
* @returns {number}
|
* @returns {number}
|
||||||
*/
|
*/
|
||||||
export function getLatestEndTime(planJson) {
|
export function getLatestEndTime(planJson) {
|
||||||
|
@ -27,8 +27,8 @@ import { expect } from '../pluginFixtures.js';
|
|||||||
* Given a canvas and a set of points, tags the points on the canvas.
|
* Given a canvas and a set of points, tags the points on the canvas.
|
||||||
* @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}
|
||||||
*/
|
*/
|
||||||
export async function createTags({ page, canvas, xEnd = 700, yEnd = 520 }) {
|
export async function createTags({ page, canvas, xEnd = 700, yEnd = 520 }) {
|
||||||
|
8
e2e/index.js
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
// Import everything from the specific fixture files
|
||||||
|
import * as appActions from './appActions.js';
|
||||||
|
import * as avpFixtures from './avpFixtures.js';
|
||||||
|
import * as baseFixtures from './baseFixtures.js';
|
||||||
|
import * as pluginFixtures from './pluginFixtures.js';
|
||||||
|
|
||||||
|
// Export these as named exports
|
||||||
|
export { appActions, avpFixtures, baseFixtures, pluginFixtures };
|
1449
e2e/package-lock.json
generated
Normal file
27
e2e/package.json
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
{
|
||||||
|
"name": "openmct-e2e",
|
||||||
|
"version": "4.0.0-next",
|
||||||
|
"description": "The Open MCT e2e framework",
|
||||||
|
"type": "module",
|
||||||
|
"module": "index.js",
|
||||||
|
"exports": {
|
||||||
|
".": {
|
||||||
|
"import": "./index.js"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"scripts": {
|
||||||
|
"pretest:visual": "npm install",
|
||||||
|
"test": "npx playwright test",
|
||||||
|
"test:visual": "percy exec"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@types/sinonjs__fake-timers": "8.1.5",
|
||||||
|
"@percy/cli": "1.27.4",
|
||||||
|
"@percy/playwright": "1.0.4",
|
||||||
|
"@playwright/test": "1.44.0",
|
||||||
|
"@axe-core/playwright": "4.8.5",
|
||||||
|
"sinon": "17.0.0"
|
||||||
|
},
|
||||||
|
"author": "NASA Ames Research Center",
|
||||||
|
"license": "Apache-2.0"
|
||||||
|
}
|
@ -3,6 +3,7 @@
|
|||||||
|
|
||||||
// eslint-disable-next-line no-unused-vars
|
// eslint-disable-next-line no-unused-vars
|
||||||
import { devices } from '@playwright/test';
|
import { devices } from '@playwright/test';
|
||||||
|
import { fileURLToPath } from 'url';
|
||||||
const MAX_FAILURES = 5;
|
const MAX_FAILURES = 5;
|
||||||
const NUM_WORKERS = 2;
|
const NUM_WORKERS = 2;
|
||||||
|
|
||||||
@ -15,6 +16,7 @@ const config = {
|
|||||||
timeout: 60 * 1000,
|
timeout: 60 * 1000,
|
||||||
webServer: {
|
webServer: {
|
||||||
command: 'npm run start:coverage',
|
command: 'npm run start:coverage',
|
||||||
|
cwd: fileURLToPath(new URL('../', import.meta.url)), // Provide cwd for the root of the project
|
||||||
url: 'http://localhost:8080/#',
|
url: 'http://localhost:8080/#',
|
||||||
timeout: 200 * 1000,
|
timeout: 200 * 1000,
|
||||||
reuseExistingServer: true //This was originally disabled to prevent differences in local debugging vs. CI. However, it significantly speeds up local debugging.
|
reuseExistingServer: true //This was originally disabled to prevent differences in local debugging vs. CI. However, it significantly speeds up local debugging.
|
||||||
@ -27,7 +29,9 @@ const config = {
|
|||||||
ignoreHTTPSErrors: true,
|
ignoreHTTPSErrors: true,
|
||||||
screenshot: 'only-on-failure',
|
screenshot: 'only-on-failure',
|
||||||
trace: 'on-first-retry',
|
trace: 'on-first-retry',
|
||||||
video: 'off'
|
video: 'off',
|
||||||
|
// @ts-ignore - custom configuration option for nyc codecoverage output path
|
||||||
|
coveragePath: fileURLToPath(new URL('../.nyc_output', import.meta.url))
|
||||||
},
|
},
|
||||||
projects: [
|
projects: [
|
||||||
{
|
{
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
// playwright.config.js
|
// playwright.config.js
|
||||||
// @ts-check
|
// @ts-check
|
||||||
|
import { fileURLToPath } from 'url';
|
||||||
/** @type {import('@playwright/test').PlaywrightTestConfig} */
|
/** @type {import('@playwright/test').PlaywrightTestConfig} */
|
||||||
const config = {
|
const config = {
|
||||||
retries: 0,
|
retries: 0,
|
||||||
@ -10,6 +10,7 @@ const config = {
|
|||||||
timeout: 30 * 1000,
|
timeout: 30 * 1000,
|
||||||
webServer: {
|
webServer: {
|
||||||
command: 'npm run start:coverage',
|
command: 'npm run start:coverage',
|
||||||
|
cwd: fileURLToPath(new URL('../', import.meta.url)), // Provide cwd for the root of the project
|
||||||
url: 'http://localhost:8080/#',
|
url: 'http://localhost:8080/#',
|
||||||
timeout: 120 * 1000,
|
timeout: 120 * 1000,
|
||||||
reuseExistingServer: true
|
reuseExistingServer: true
|
||||||
|
@ -14,6 +14,7 @@ const config = {
|
|||||||
timeout: 30 * 1000,
|
timeout: 30 * 1000,
|
||||||
webServer: {
|
webServer: {
|
||||||
command: 'npm run start:coverage',
|
command: 'npm run start:coverage',
|
||||||
|
cwd: fileURLToPath(new URL('../', import.meta.url)), // Provide cwd for the root of the project
|
||||||
url: 'http://localhost:8080/#',
|
url: 'http://localhost:8080/#',
|
||||||
timeout: 200 * 1000,
|
timeout: 200 * 1000,
|
||||||
reuseExistingServer: true //This was originally disabled to prevent differences in local debugging vs. CI. However, it significantly speeds up local debugging.
|
reuseExistingServer: true //This was originally disabled to prevent differences in local debugging vs. CI. However, it significantly speeds up local debugging.
|
||||||
@ -27,7 +28,9 @@ const config = {
|
|||||||
ignoreHTTPSErrors: true,
|
ignoreHTTPSErrors: true,
|
||||||
screenshot: 'only-on-failure',
|
screenshot: 'only-on-failure',
|
||||||
trace: 'on-first-retry',
|
trace: 'on-first-retry',
|
||||||
video: 'off'
|
video: 'off',
|
||||||
|
// @ts-ignore - custom configuration option for nyc codecoverage output path
|
||||||
|
coveragePath: fileURLToPath(new URL('../.nyc_output', import.meta.url))
|
||||||
},
|
},
|
||||||
projects: [
|
projects: [
|
||||||
{
|
{
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
// playwright.config.js
|
// playwright.config.js
|
||||||
// @ts-check
|
// @ts-check
|
||||||
|
import { fileURLToPath } from 'url';
|
||||||
/** @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'
|
||||||
@ -10,6 +10,7 @@ const config = {
|
|||||||
workers: 1, //Only run in serial with 1 worker
|
workers: 1, //Only run in serial with 1 worker
|
||||||
webServer: {
|
webServer: {
|
||||||
command: 'npm run start', //need development mode for performance.marks and others
|
command: 'npm run start', //need development mode for performance.marks and others
|
||||||
|
cwd: fileURLToPath(new URL('../', import.meta.url)), // Provide cwd for the root of the project
|
||||||
url: 'http://localhost:8080/#',
|
url: 'http://localhost:8080/#',
|
||||||
timeout: 200 * 1000,
|
timeout: 200 * 1000,
|
||||||
reuseExistingServer: false
|
reuseExistingServer: false
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
// playwright.config.js
|
// playwright.config.js
|
||||||
// @ts-check
|
// @ts-check
|
||||||
|
import { fileURLToPath } from 'url';
|
||||||
/** @type {import('@playwright/test').PlaywrightTestConfig} */
|
/** @type {import('@playwright/test').PlaywrightTestConfig} */
|
||||||
const config = {
|
const config = {
|
||||||
retries: 0, //Only for debugging purposes for trace: 'on-first-retry'
|
retries: 0, //Only for debugging purposes for trace: 'on-first-retry'
|
||||||
@ -10,6 +10,7 @@ const config = {
|
|||||||
workers: 1, //Only run in serial with 1 worker
|
workers: 1, //Only run in serial with 1 worker
|
||||||
webServer: {
|
webServer: {
|
||||||
command: 'npm run start:prod', //Production mode
|
command: 'npm run start:prod', //Production mode
|
||||||
|
cwd: fileURLToPath(new URL('../', import.meta.url)), // Provide cwd for the root of the project
|
||||||
url: 'http://localhost:8080/#',
|
url: 'http://localhost:8080/#',
|
||||||
timeout: 200 * 1000,
|
timeout: 200 * 1000,
|
||||||
reuseExistingServer: false //Must be run with this option to prevent dev mode
|
reuseExistingServer: false //Must be run with this option to prevent dev mode
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
/* eslint-disable no-undef */
|
|
||||||
// playwright.config.js
|
// playwright.config.js
|
||||||
// @ts-check
|
// @ts-check
|
||||||
|
import { fileURLToPath } from 'url';
|
||||||
/** @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
|
||||||
@ -11,6 +10,7 @@ const config = {
|
|||||||
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',
|
||||||
|
cwd: fileURLToPath(new URL('../', import.meta.url)), // Provide cwd for the root of the project
|
||||||
url: 'http://localhost:8080/#',
|
url: 'http://localhost:8080/#',
|
||||||
timeout: 200 * 1000,
|
timeout: 200 * 1000,
|
||||||
reuseExistingServer: !process.env.CI
|
reuseExistingServer: !process.env.CI
|
||||||
@ -36,6 +36,13 @@ const config = {
|
|||||||
browserName: 'chromium',
|
browserName: 'chromium',
|
||||||
theme: 'snow'
|
theme: 'snow'
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'darkmatter-theme', //Runs the same visual tests but with darkmatter-theme
|
||||||
|
use: {
|
||||||
|
browserName: 'chromium',
|
||||||
|
theme: 'darkmatter-theme'
|
||||||
|
}
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
reporter: [
|
reporter: [
|
||||||
|
@ -1,6 +1,5 @@
|
|||||||
// playwright.config.js
|
// playwright.config.js
|
||||||
// @ts-check
|
// @ts-check
|
||||||
|
|
||||||
import { devices } from '@playwright/test';
|
import { devices } from '@playwright/test';
|
||||||
import { fileURLToPath } from 'url';
|
import { fileURLToPath } from 'url';
|
||||||
|
|
||||||
@ -11,6 +10,7 @@ const config = {
|
|||||||
timeout: 60 * 1000,
|
timeout: 60 * 1000,
|
||||||
webServer: {
|
webServer: {
|
||||||
command: 'npm run start', //Start in dev mode for hot reloading
|
command: 'npm run start', //Start in dev mode for hot reloading
|
||||||
|
cwd: fileURLToPath(new URL('../', import.meta.url)), // Provide cwd for the root of the project
|
||||||
url: 'http://localhost:8080/#',
|
url: 'http://localhost:8080/#',
|
||||||
timeout: 200 * 1000,
|
timeout: 200 * 1000,
|
||||||
reuseExistingServer: true //This was originally disabled to prevent differences in local debugging vs. CI. However, it significantly speeds up local debugging.
|
reuseExistingServer: true //This was originally disabled to prevent differences in local debugging vs. CI. However, it significantly speeds up local debugging.
|
||||||
|
@ -1,4 +1,3 @@
|
|||||||
/* eslint-disable no-undef */
|
|
||||||
/*****************************************************************************
|
/*****************************************************************************
|
||||||
* Open MCT, Copyright (c) 2014-2024, United States Government
|
* Open MCT, Copyright (c) 2014-2024, United States Government
|
||||||
* as represented by the Administrator of the National Aeronautics and Space
|
* as represented by the Administrator of the National Aeronautics and Space
|
||||||
@ -123,7 +122,6 @@ const extendedTest = test.extend({
|
|||||||
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
|
|
||||||
if (theme === 'snow') {
|
if (theme === 'snow') {
|
||||||
//inject snow theme
|
//inject snow theme
|
||||||
await page.addInitScript({
|
await page.addInitScript({
|
||||||
|
26
e2e/test-data/display_layout_with_child_overlay_plot.json
Normal file
@ -174,6 +174,6 @@ test.describe('AppActions', () => {
|
|||||||
type: 'Folder'
|
type: 'Folder'
|
||||||
});
|
});
|
||||||
await openObjectTreeContextMenu(page, folder.url);
|
await openObjectTreeContextMenu(page, folder.url);
|
||||||
await expect(page.getByLabel('Menu')).toBeVisible();
|
await expect(page.getByLabel(`${folder.name} Context Menu`)).toBeVisible();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -26,11 +26,12 @@ relates to how we've extended it (i.e. ./e2e/baseFixtures.js) and assumptions ma
|
|||||||
(`npm start` and ./e2e/webpack-dev-middleware.js)
|
(`npm start` and ./e2e/webpack-dev-middleware.js)
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { test } from '../../baseFixtures.js';
|
import { expect, test } from '../../baseFixtures.js';
|
||||||
|
import { MISSION_TIME } from '../../constants.js';
|
||||||
|
|
||||||
test.describe('baseFixtures tests', () => {
|
test.describe('baseFixtures tests', () => {
|
||||||
//Skip this test for now https://github.com/nasa/openmct/issues/6785
|
//Skip this test for now https://github.com/nasa/openmct/issues/6785
|
||||||
test.fixme('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' });
|
||||||
@ -52,3 +53,27 @@ test.describe('baseFixtures tests', () => {
|
|||||||
]);
|
]);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test.describe('baseFixtures tests @clock', () => {
|
||||||
|
test.use({
|
||||||
|
clockOptions: {
|
||||||
|
now: MISSION_TIME,
|
||||||
|
shouldAdvanceTime: false
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
test.beforeEach(async ({ page }) => {
|
||||||
|
await page.goto('./', { waitUntil: 'domcontentloaded' });
|
||||||
|
});
|
||||||
|
|
||||||
|
test('Can use clockOptions and tick fixtures to control the clock', async ({ page, tick }) => {
|
||||||
|
let time = await page.evaluate(() => new Date().getTime());
|
||||||
|
expect(time).toBe(MISSION_TIME);
|
||||||
|
await tick(1000);
|
||||||
|
time = await page.evaluate(() => new Date().getTime());
|
||||||
|
expect(time).toBe(MISSION_TIME + 1000 * 1);
|
||||||
|
await tick(1000);
|
||||||
|
time = await page.evaluate(() => new Date().getTime());
|
||||||
|
expect(time).toBe(MISSION_TIME + 1000 * 2);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
@ -33,7 +33,12 @@
|
|||||||
|
|
||||||
import { fileURLToPath } from 'url';
|
import { fileURLToPath } from 'url';
|
||||||
|
|
||||||
import { createDomainObjectWithDefaults, createExampleTelemetryObject } from '../../appActions.js';
|
import {
|
||||||
|
createDomainObjectWithDefaults,
|
||||||
|
createExampleTelemetryObject,
|
||||||
|
setIndependentTimeConductorBounds,
|
||||||
|
setTimeConductorBounds
|
||||||
|
} from '../../appActions.js';
|
||||||
import { MISSION_TIME } from '../../constants.js';
|
import { MISSION_TIME } from '../../constants.js';
|
||||||
import { expect, test } from '../../pluginFixtures.js';
|
import { expect, test } from '../../pluginFixtures.js';
|
||||||
|
|
||||||
@ -89,6 +94,53 @@ test.describe('Generate Visual Test Data @localStorage @generatedata @clock', ()
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('Generate display layout with 1 child overlay plot', async ({ page, context }) => {
|
||||||
|
const parent = await createDomainObjectWithDefaults(page, {
|
||||||
|
type: 'Display Layout',
|
||||||
|
name: 'Parent Display Layout'
|
||||||
|
});
|
||||||
|
const overlayPlot = await createDomainObjectWithDefaults(page, {
|
||||||
|
type: 'Overlay Plot',
|
||||||
|
name: 'Child Overlay Plot 1',
|
||||||
|
parent: parent.uuid
|
||||||
|
});
|
||||||
|
await createDomainObjectWithDefaults(page, {
|
||||||
|
type: 'Sine Wave Generator',
|
||||||
|
name: 'Child SWG 1',
|
||||||
|
parent: overlayPlot.uuid
|
||||||
|
});
|
||||||
|
|
||||||
|
await page.goto(parent.url, { waitUntil: 'domcontentloaded' });
|
||||||
|
|
||||||
|
await setIndependentTimeConductorBounds(page, {
|
||||||
|
start: '2024-11-12 19:11:11.000Z',
|
||||||
|
end: '2024-11-12 20:11:11.000Z'
|
||||||
|
});
|
||||||
|
|
||||||
|
const NEW_GLOBAL_START_BOUNDS = '2024-11-11 19:11:11.000Z';
|
||||||
|
const NEW_GLOBAL_END_BOUNDS = '2024-11-11 20:11:11.000Z';
|
||||||
|
|
||||||
|
await setTimeConductorBounds(page, NEW_GLOBAL_START_BOUNDS, NEW_GLOBAL_END_BOUNDS);
|
||||||
|
|
||||||
|
// Verify that the global time conductor bounds have been updated
|
||||||
|
expect(
|
||||||
|
await page.getByLabel('Global Time Conductor').getByLabel('Start bounds').textContent()
|
||||||
|
).toEqual(NEW_GLOBAL_START_BOUNDS);
|
||||||
|
expect(
|
||||||
|
await page.getByLabel('Global Time Conductor').getByLabel('End bounds').textContent()
|
||||||
|
).toEqual(NEW_GLOBAL_END_BOUNDS);
|
||||||
|
|
||||||
|
//Save localStorage for future test execution
|
||||||
|
await context.storageState({
|
||||||
|
path: fileURLToPath(
|
||||||
|
new URL(
|
||||||
|
'../../../e2e/test-data/display_layout_with_child_overlay_plot.json',
|
||||||
|
import.meta.url
|
||||||
|
)
|
||||||
|
)
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
test('Generate flexible layout with 2 child display layouts', async ({ page, context }) => {
|
test('Generate flexible layout with 2 child display layouts', async ({ page, context }) => {
|
||||||
// Create Display Layout
|
// Create Display Layout
|
||||||
const parent = await createDomainObjectWithDefaults(page, {
|
const parent = await createDomainObjectWithDefaults(page, {
|
||||||
|
@ -31,8 +31,8 @@ import { createDomainObjectWithDefaults } from '../../appActions.js';
|
|||||||
import { expect, test } from '../../pluginFixtures.js';
|
import { expect, test } from '../../pluginFixtures.js';
|
||||||
|
|
||||||
const TEST_FOLDER = 'test folder';
|
const TEST_FOLDER = 'test folder';
|
||||||
const jsonFilePath = 'e2e/test-data/ExampleLayouts.json';
|
const jsonFilePath = 'test-data/ExampleLayouts.json';
|
||||||
const imageFilePath = 'e2e/test-data/rick.jpg';
|
const imageFilePath = '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 ({
|
test('Required Field indicators appear if title is empty and can be corrected', async ({
|
||||||
|
@ -21,7 +21,6 @@
|
|||||||
*****************************************************************************/
|
*****************************************************************************/
|
||||||
import fs from 'fs';
|
import fs from 'fs';
|
||||||
|
|
||||||
import { getPreciseDuration } from '../../../../src/utils/duration.js';
|
|
||||||
import { createDomainObjectWithDefaults, createPlanFromJSON } from '../../../appActions.js';
|
import { createDomainObjectWithDefaults, createPlanFromJSON } from '../../../appActions.js';
|
||||||
import {
|
import {
|
||||||
assertPlanActivities,
|
assertPlanActivities,
|
||||||
@ -132,3 +131,58 @@ test.describe('Gantt Chart', () => {
|
|||||||
);
|
);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const ONE_SECOND = 1000;
|
||||||
|
const ONE_MINUTE = 60 * ONE_SECOND;
|
||||||
|
const ONE_HOUR = ONE_MINUTE * 60;
|
||||||
|
const ONE_DAY = ONE_HOUR * 24;
|
||||||
|
|
||||||
|
function normalizeAge(num) {
|
||||||
|
const hundredtized = num * 100;
|
||||||
|
const isWhole = hundredtized % 100 === 0;
|
||||||
|
|
||||||
|
return isWhole ? hundredtized / 100 : num;
|
||||||
|
}
|
||||||
|
|
||||||
|
function padLeadingZeros(num, numOfLeadingZeros) {
|
||||||
|
return num.toString().padStart(numOfLeadingZeros, '0');
|
||||||
|
}
|
||||||
|
|
||||||
|
function toDoubleDigits(num) {
|
||||||
|
return padLeadingZeros(num, 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
function toTripleDigits(num) {
|
||||||
|
return padLeadingZeros(num, 3);
|
||||||
|
}
|
||||||
|
|
||||||
|
function getPreciseDuration(value, { excludeMilliSeconds, useDayFormat } = {}) {
|
||||||
|
let preciseDuration;
|
||||||
|
const ms = value || 0;
|
||||||
|
|
||||||
|
const duration = [
|
||||||
|
Math.floor(normalizeAge(ms / ONE_DAY)),
|
||||||
|
toDoubleDigits(Math.floor(normalizeAge((ms % ONE_DAY) / ONE_HOUR))),
|
||||||
|
toDoubleDigits(Math.floor(normalizeAge((ms % ONE_HOUR) / ONE_MINUTE))),
|
||||||
|
toDoubleDigits(Math.floor(normalizeAge((ms % ONE_MINUTE) / ONE_SECOND)))
|
||||||
|
];
|
||||||
|
if (!excludeMilliSeconds) {
|
||||||
|
duration.push(toTripleDigits(Math.floor(normalizeAge(ms % ONE_SECOND))));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (useDayFormat) {
|
||||||
|
// Format days as XD
|
||||||
|
const days = duration.shift();
|
||||||
|
if (days > 0) {
|
||||||
|
preciseDuration = `${days}D ${duration.join(':')}`;
|
||||||
|
} else {
|
||||||
|
preciseDuration = duration.join(':');
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
const days = toDoubleDigits(duration.shift());
|
||||||
|
duration.unshift(days);
|
||||||
|
preciseDuration = duration.join(':');
|
||||||
|
}
|
||||||
|
|
||||||
|
return preciseDuration;
|
||||||
|
}
|
||||||
|
@ -131,7 +131,10 @@ test.describe('Time Strip', () => {
|
|||||||
const startBoundString = new Date(startBound).toISOString().replace('T', ' ');
|
const startBoundString = new Date(startBound).toISOString().replace('T', ' ');
|
||||||
const endBoundString = new Date(endBound).toISOString().replace('T', ' ');
|
const endBoundString = new Date(endBound).toISOString().replace('T', ' ');
|
||||||
|
|
||||||
await setIndependentTimeConductorBounds(page, startBoundString, endBoundString);
|
await setIndependentTimeConductorBounds(page, {
|
||||||
|
start: startBoundString,
|
||||||
|
end: endBoundString
|
||||||
|
});
|
||||||
expect(await activityBounds.count()).toEqual(1);
|
expect(await activityBounds.count()).toEqual(1);
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -160,7 +163,10 @@ test.describe('Time Strip', () => {
|
|||||||
const startBoundString = new Date(startBound).toISOString().replace('T', ' ');
|
const startBoundString = new Date(startBound).toISOString().replace('T', ' ');
|
||||||
const endBoundString = new Date(endBound).toISOString().replace('T', ' ');
|
const endBoundString = new Date(endBound).toISOString().replace('T', ' ');
|
||||||
|
|
||||||
await setIndependentTimeConductorBounds(page, startBoundString, endBoundString);
|
await setIndependentTimeConductorBounds(page, {
|
||||||
|
start: startBoundString,
|
||||||
|
end: endBoundString
|
||||||
|
});
|
||||||
|
|
||||||
// Verify that two events are displayed
|
// Verify that two events are displayed
|
||||||
expect(await activityBounds.count()).toEqual(2);
|
expect(await activityBounds.count()).toEqual(2);
|
||||||
|
@ -286,12 +286,22 @@ test.describe('Basic Condition Set Use', () => {
|
|||||||
await page.locator('button[title="Save"]').click();
|
await page.locator('button[title="Save"]').click();
|
||||||
await page.getByRole('listitem', { name: 'Save and Finish Editing' }).click();
|
await page.getByRole('listitem', { name: 'Save and Finish Editing' }).click();
|
||||||
|
|
||||||
await page.click('button[title="Change the current view"]');
|
await page.getByLabel('Open the View Switcher Menu').click();
|
||||||
|
|
||||||
await expect(page.getByRole('menuitem', { name: /Lad Table/ })).toBeHidden();
|
await expect(page.getByRole('menuitem', { name: /Lad Table/ })).toBeHidden();
|
||||||
await expect(page.getByRole('menuitem', { name: /Conditions View/ })).toBeVisible();
|
await expect(page.getByRole('menuitem', { name: /Conditions View/ })).toBeVisible();
|
||||||
await expect(page.getByRole('menuitem', { name: /Plot/ })).toBeVisible();
|
await expect(page.getByRole('menuitem', { name: /Plot/ })).toBeVisible();
|
||||||
await expect(page.getByRole('menuitem', { name: /Telemetry Table/ })).toBeVisible();
|
await expect(page.getByRole('menuitem', { name: /Telemetry Table/ })).toBeVisible();
|
||||||
|
await page.getByLabel('Plot').click();
|
||||||
|
await expect(
|
||||||
|
page.getByLabel('Plot Legend Collapsed').getByText('Test Condition Set')
|
||||||
|
).toBeVisible();
|
||||||
|
await page.getByLabel('Open the View Switcher Menu').click();
|
||||||
|
await page.getByLabel('Telemetry Table').click();
|
||||||
|
await expect(page.getByRole('searchbox', { name: 'output filter input' })).toBeVisible();
|
||||||
|
await page.getByLabel('Open the View Switcher Menu').click();
|
||||||
|
await page.getByLabel('Conditions View').click();
|
||||||
|
await expect(page.getByText('Current Output')).toBeVisible();
|
||||||
});
|
});
|
||||||
test('ConditionSet has correct outputs when telemetry is and is not available', async ({
|
test('ConditionSet has correct outputs when telemetry is and is not available', async ({
|
||||||
page
|
page
|
||||||
@ -361,7 +371,7 @@ test.describe('Basic Condition Set Use', () => {
|
|||||||
|
|
||||||
// Validate that the condition set is evaluating and outputting
|
// Validate that the condition set is evaluating and outputting
|
||||||
// the correct value when the underlying telemetry subscription is active.
|
// the correct value when the underlying telemetry subscription is active.
|
||||||
let outputValue = page.locator('[aria-label="Current Output Value"]');
|
let outputValue = page.getByLabel('Current Output Value');
|
||||||
await expect(outputValue).toHaveText('false');
|
await expect(outputValue).toHaveText('false');
|
||||||
|
|
||||||
await page.goto(exampleTelemetry.url);
|
await page.goto(exampleTelemetry.url);
|
||||||
@ -452,9 +462,94 @@ test.describe('Basic Condition Set Use', () => {
|
|||||||
|
|
||||||
// Validate that the condition set is evaluating and outputting
|
// Validate that the condition set is evaluating and outputting
|
||||||
// the correct value when the underlying telemetry subscription is active.
|
// the correct value when the underlying telemetry subscription is active.
|
||||||
let outputValue = page.locator('[aria-label="Current Output Value"]');
|
let outputValue = page.getByLabel('Current Output Value');
|
||||||
await expect(outputValue).toHaveText('false');
|
await expect(outputValue).toHaveText('false');
|
||||||
|
|
||||||
await page.goto(exampleTelemetry.url);
|
await page.goto(exampleTelemetry.url);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test.fixme('Ensure condition sets work with telemetry like operator status', ({ page }) => {
|
||||||
|
test.info().annotations.push({
|
||||||
|
type: 'issue',
|
||||||
|
description: 'https://github.com/nasa/openmct/issues/7484'
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
test.describe('Condition Set Composition', () => {
|
||||||
|
let conditionSet;
|
||||||
|
let exampleTelemetry;
|
||||||
|
|
||||||
|
test.beforeEach(async ({ page }) => {
|
||||||
|
await page.goto('./', { waitUntil: 'domcontentloaded' });
|
||||||
|
|
||||||
|
// Create Condition Set
|
||||||
|
conditionSet = await createDomainObjectWithDefaults(page, {
|
||||||
|
type: 'Condition Set'
|
||||||
|
});
|
||||||
|
|
||||||
|
// Create Telemetry Object as child to Condition Set
|
||||||
|
exampleTelemetry = await createExampleTelemetryObject(page, conditionSet.uuid);
|
||||||
|
|
||||||
|
// Edit Condition Set
|
||||||
|
await page.goto(conditionSet.url);
|
||||||
|
await page.getByRole('button', { name: 'Edit Object' }).click();
|
||||||
|
|
||||||
|
// Add Condition to Condition Set
|
||||||
|
await page.getByRole('button', { name: 'Add Condition' }).click();
|
||||||
|
|
||||||
|
// Enter Condition Output
|
||||||
|
await page.getByLabel('Condition Name Input').first().fill('Negative');
|
||||||
|
await page.getByLabel('Condition Output Type').first().selectOption({ value: 'string' });
|
||||||
|
await page.getByLabel('Condition Output String').first().fill('Negative');
|
||||||
|
|
||||||
|
// Condition Trigger default is okay so no change needed to form
|
||||||
|
|
||||||
|
// Enter Condition Criterion
|
||||||
|
await page.getByLabel('Criterion Telemetry Selection').first().selectOption({ value: 'all' });
|
||||||
|
await page.getByLabel('Criterion Metadata Selection').first().selectOption({ value: 'sin' });
|
||||||
|
await page
|
||||||
|
.locator('select[aria-label="Criterion Comparison Selection"]')
|
||||||
|
.first()
|
||||||
|
.selectOption({ value: 'lessThan' });
|
||||||
|
await page.getByLabel('Criterion Input').first().fill('0');
|
||||||
|
|
||||||
|
// Save the Condition Set
|
||||||
|
await page.getByRole('button', { name: 'Save' }).click();
|
||||||
|
await page.getByRole('listitem', { name: 'Save and Finish Editing' }).click();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('You can remove telemetry from a condition set with existing conditions', async ({
|
||||||
|
page
|
||||||
|
}) => {
|
||||||
|
test.info().annotations.push({
|
||||||
|
type: 'issue',
|
||||||
|
description: 'https://github.com/nasa/openmct/issues/7710'
|
||||||
|
});
|
||||||
|
|
||||||
|
await page.getByLabel('Expand My Items folder').click();
|
||||||
|
await page.getByLabel(`Expand ${conditionSet.name} conditionSet`).click();
|
||||||
|
|
||||||
|
await page
|
||||||
|
.getByLabel(`Navigate to ${exampleTelemetry.name}`, { exact: false })
|
||||||
|
.click({ button: 'right' });
|
||||||
|
|
||||||
|
await page
|
||||||
|
.getByLabel(`${exampleTelemetry.name} Context Menu`)
|
||||||
|
.getByRole('menuitem', { name: 'Remove' })
|
||||||
|
.click();
|
||||||
|
await page.getByRole('button', { name: 'OK', exact: true }).click();
|
||||||
|
|
||||||
|
await page
|
||||||
|
.getByLabel(`Navigate to ${conditionSet.name} conditionSet Object`, { exact: true })
|
||||||
|
.click();
|
||||||
|
await page.getByRole('button', { name: 'Edit Object' }).click();
|
||||||
|
await page.getByRole('tab', { name: 'Elements' }).click();
|
||||||
|
expect(
|
||||||
|
await page
|
||||||
|
.getByRole('tabpanel', { name: 'Inspector Views' })
|
||||||
|
.getByRole('listitem', { name: exampleTelemetry.name })
|
||||||
|
.count()
|
||||||
|
).toEqual(0);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
@ -23,6 +23,7 @@ import { fileURLToPath } from 'url';
|
|||||||
|
|
||||||
import {
|
import {
|
||||||
createDomainObjectWithDefaults,
|
createDomainObjectWithDefaults,
|
||||||
|
navigateToObjectWithFixedTimeBounds,
|
||||||
setFixedTimeMode,
|
setFixedTimeMode,
|
||||||
setIndependentTimeConductorBounds,
|
setIndependentTimeConductorBounds,
|
||||||
setRealTimeMode,
|
setRealTimeMode,
|
||||||
@ -30,12 +31,120 @@ import {
|
|||||||
} from '../../../../appActions.js';
|
} from '../../../../appActions.js';
|
||||||
import { expect, test } from '../../../../pluginFixtures.js';
|
import { expect, test } from '../../../../pluginFixtures.js';
|
||||||
|
|
||||||
const LOCALSTORAGE_PATH = fileURLToPath(
|
const CHILD_LAYOUT_STORAGE_STATE_PATH = fileURLToPath(
|
||||||
new URL('../../../../test-data/display_layout_with_child_layouts.json', import.meta.url)
|
new URL('../../../../test-data/display_layout_with_child_layouts.json', import.meta.url)
|
||||||
);
|
);
|
||||||
|
const CHILD_PLOT_STORAGE_STATE_PATH = fileURLToPath(
|
||||||
|
new URL('../../../../test-data/display_layout_with_child_overlay_plot.json', import.meta.url)
|
||||||
|
);
|
||||||
const TINY_IMAGE_BASE64 =
|
const TINY_IMAGE_BASE64 =
|
||||||
'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAgAAAAIAQMAAAD+wSzIAAAABlBMVEX///+/v7+jQ3Y5AAAADklEQVQI12P4AIX8EAgALgAD/aNpbtEAAAAASUVORK5CYII';
|
'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAgAAAAIAQMAAAD+wSzIAAAABlBMVEX///+/v7+jQ3Y5AAAADklEQVQI12P4AIX8EAgALgAD/aNpbtEAAAAASUVORK5CYII';
|
||||||
|
|
||||||
|
test.describe('Display Layout Sub-object Actions @localStorage', () => {
|
||||||
|
const INIT_ITC_START_BOUNDS = '2024-11-12 19:11:11.000Z';
|
||||||
|
const INIT_ITC_END_BOUNDS = '2024-11-12 20:11:11.000Z';
|
||||||
|
const NEW_GLOBAL_START_BOUNDS = '2024-11-11 19:11:11.000Z';
|
||||||
|
const NEW_GLOBAL_END_BOUNDS = '2024-11-11 20:11:11.000Z';
|
||||||
|
|
||||||
|
test.use({
|
||||||
|
storageState: CHILD_PLOT_STORAGE_STATE_PATH
|
||||||
|
});
|
||||||
|
|
||||||
|
test.beforeEach(async ({ page }) => {
|
||||||
|
await page.goto('./', { waitUntil: 'domcontentloaded' });
|
||||||
|
await page.getByLabel('Expand My Items folder').click();
|
||||||
|
const waitForMyItemsNavigation = page.waitForURL(`**/mine/?*`);
|
||||||
|
await page
|
||||||
|
.getByLabel('Main Tree')
|
||||||
|
.getByLabel('Navigate to Parent Display Layout layout Object')
|
||||||
|
.click();
|
||||||
|
// Wait for the URL to change to the display layout
|
||||||
|
await waitForMyItemsNavigation;
|
||||||
|
});
|
||||||
|
test('Open in New Tab action preserves time bounds @2p', async ({ page }) => {
|
||||||
|
test.info().annotations.push({
|
||||||
|
type: 'issue',
|
||||||
|
description: 'https://github.com/nasa/openmct/issues/7524'
|
||||||
|
});
|
||||||
|
test.info().annotations.push({
|
||||||
|
type: 'issue',
|
||||||
|
description: 'https://github.com/nasa/openmct/issues/6982'
|
||||||
|
});
|
||||||
|
|
||||||
|
const TEST_FIXED_START_TIME = 1731352271000; // 2024-11-11 19:11:11.000Z
|
||||||
|
const TEST_FIXED_END_TIME = TEST_FIXED_START_TIME + 3600000; // 2024-11-11 20:11:11.000Z
|
||||||
|
|
||||||
|
// Verify the ITC has the expected initial bounds
|
||||||
|
expect(
|
||||||
|
await page
|
||||||
|
.getByLabel('Child Overlay Plot 1 Frame Controls')
|
||||||
|
.getByLabel('Start bounds')
|
||||||
|
.textContent()
|
||||||
|
).toEqual(INIT_ITC_START_BOUNDS);
|
||||||
|
expect(
|
||||||
|
await page
|
||||||
|
.getByLabel('Child Overlay Plot 1 Frame Controls')
|
||||||
|
.getByLabel('End bounds')
|
||||||
|
.textContent()
|
||||||
|
).toEqual(INIT_ITC_END_BOUNDS);
|
||||||
|
|
||||||
|
// Update the global fixed bounds to 2024-11-11 19:11:11.000Z / 2024-11-11 20:11:11.000Z
|
||||||
|
const url = page.url().split('?')[0];
|
||||||
|
await navigateToObjectWithFixedTimeBounds(
|
||||||
|
page,
|
||||||
|
url,
|
||||||
|
TEST_FIXED_START_TIME,
|
||||||
|
TEST_FIXED_END_TIME
|
||||||
|
);
|
||||||
|
|
||||||
|
// ITC bounds should still match the initial ITC bounds
|
||||||
|
expect(
|
||||||
|
await page
|
||||||
|
.getByLabel('Child Overlay Plot 1 Frame Controls')
|
||||||
|
.getByLabel('Start bounds')
|
||||||
|
.textContent()
|
||||||
|
).toEqual(INIT_ITC_START_BOUNDS);
|
||||||
|
expect(
|
||||||
|
await page
|
||||||
|
.getByLabel('Child Overlay Plot 1 Frame Controls')
|
||||||
|
.getByLabel('End bounds')
|
||||||
|
.textContent()
|
||||||
|
).toEqual(INIT_ITC_END_BOUNDS);
|
||||||
|
|
||||||
|
// Open the Child Overlay Plot 1 in a new tab
|
||||||
|
await page.getByLabel('View menu items').click();
|
||||||
|
const pagePromise = page.context().waitForEvent('page');
|
||||||
|
await page.getByLabel('Open In New Tab').click();
|
||||||
|
|
||||||
|
const newPage = await pagePromise;
|
||||||
|
await newPage.waitForLoadState('domcontentloaded');
|
||||||
|
|
||||||
|
// Verify that the global time conductor bounds in the new page match the updated global bounds
|
||||||
|
expect(
|
||||||
|
await newPage.getByLabel('Global Time Conductor').getByLabel('Start bounds').textContent()
|
||||||
|
).toEqual(NEW_GLOBAL_START_BOUNDS);
|
||||||
|
expect(
|
||||||
|
await newPage.getByLabel('Global Time Conductor').getByLabel('End bounds').textContent()
|
||||||
|
).toEqual(NEW_GLOBAL_END_BOUNDS);
|
||||||
|
|
||||||
|
// Verify that the ITC is enabled in the new page
|
||||||
|
await expect(newPage.getByLabel('Disable Independent Time Conductor')).toBeVisible();
|
||||||
|
// Verify that the ITC bounds in the new page match the original ITC bounds
|
||||||
|
expect(
|
||||||
|
await newPage
|
||||||
|
.getByLabel('Independent Time Conductor Panel')
|
||||||
|
.getByLabel('Start bounds')
|
||||||
|
.textContent()
|
||||||
|
).toEqual(INIT_ITC_START_BOUNDS);
|
||||||
|
expect(
|
||||||
|
await newPage
|
||||||
|
.getByLabel('Independent Time Conductor Panel')
|
||||||
|
.getByLabel('End bounds')
|
||||||
|
.textContent()
|
||||||
|
).toEqual(INIT_ITC_END_BOUNDS);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
test.describe('Display Layout Toolbar Actions @localStorage', () => {
|
test.describe('Display Layout Toolbar Actions @localStorage', () => {
|
||||||
const PARENT_DISPLAY_LAYOUT_NAME = 'Parent Display Layout';
|
const PARENT_DISPLAY_LAYOUT_NAME = 'Parent Display Layout';
|
||||||
const CHILD_DISPLAY_LAYOUT_NAME1 = 'Child Layout 1';
|
const CHILD_DISPLAY_LAYOUT_NAME1 = 'Child Layout 1';
|
||||||
@ -50,7 +159,7 @@ test.describe('Display Layout Toolbar Actions @localStorage', () => {
|
|||||||
await page.getByLabel('Edit Object').click();
|
await page.getByLabel('Edit Object').click();
|
||||||
});
|
});
|
||||||
test.use({
|
test.use({
|
||||||
storageState: LOCALSTORAGE_PATH
|
storageState: CHILD_LAYOUT_STORAGE_STATE_PATH
|
||||||
});
|
});
|
||||||
|
|
||||||
test('can add/remove Text element to a single layout', async ({ page }) => {
|
test('can add/remove Text element to a single layout', async ({ page }) => {
|
||||||
@ -163,7 +272,7 @@ test.describe('Display Layout', () => {
|
|||||||
expect(trimmedDisplayValue).toBe(formattedTelemetryValue);
|
expect(trimmedDisplayValue).toBe(formattedTelemetryValue);
|
||||||
|
|
||||||
// ensure we can right click on the alpha-numeric widget and view historical data
|
// ensure we can right click on the alpha-numeric widget and view historical data
|
||||||
await page.getByLabel('Sine', { exact: true }).click({
|
await page.getByLabel(/Alpha-numeric telemetry value of.*/).click({
|
||||||
button: 'right'
|
button: 'right'
|
||||||
});
|
});
|
||||||
await page.getByLabel('View Historical Data').click();
|
await page.getByLabel('View Historical Data').click();
|
||||||
@ -336,7 +445,7 @@ test.describe('Display Layout', () => {
|
|||||||
|
|
||||||
const startDate = '2021-12-30 01:01:00.000Z';
|
const startDate = '2021-12-30 01:01:00.000Z';
|
||||||
const endDate = '2021-12-30 01:11:00.000Z';
|
const endDate = '2021-12-30 01:11:00.000Z';
|
||||||
await setIndependentTimeConductorBounds(page, startDate, endDate);
|
await setIndependentTimeConductorBounds(page, { start: startDate, end: endDate });
|
||||||
|
|
||||||
// check image date
|
// check image date
|
||||||
await expect(page.getByText('2021-12-30 01:11:00.000Z').first()).toBeVisible();
|
await expect(page.getByText('2021-12-30 01:11:00.000Z').first()).toBeVisible();
|
||||||
|
@ -78,8 +78,8 @@ test.describe('Flexible Layout', () => {
|
|||||||
// Expand the 'My Items' folder in the left tree
|
// Expand the 'My Items' folder in the left tree
|
||||||
await page.locator('.c-tree__item__view-control.c-disclosure-triangle').first().click();
|
await page.locator('.c-tree__item__view-control.c-disclosure-triangle').first().click();
|
||||||
// Add the Sine Wave Generator and Clock to the Flexible Layout
|
// Add the Sine Wave Generator and Clock to the Flexible Layout
|
||||||
await sineWaveGeneratorTreeItem.dragTo(page.locator('.c-fl__container.is-empty').first());
|
await sineWaveGeneratorTreeItem.dragTo(page.locator('.c-fl-container.is-empty').first());
|
||||||
await clockTreeItem.dragTo(page.locator('.c-fl__container.is-empty'));
|
await clockTreeItem.dragTo(page.locator('.c-fl-container.is-empty'));
|
||||||
// Check that panes can be dragged while Flexible Layout is in Edit mode
|
// Check that panes can be dragged while Flexible Layout is in Edit mode
|
||||||
let dragWrapper = page
|
let dragWrapper = page
|
||||||
.locator('.c-fl-container__frames-holder .c-fl-frame__drag-wrapper')
|
.locator('.c-fl-container__frames-holder .c-fl-frame__drag-wrapper')
|
||||||
@ -105,8 +105,8 @@ test.describe('Flexible Layout', () => {
|
|||||||
// Expand the 'My Items' folder in the left tree
|
// Expand the 'My Items' folder in the left tree
|
||||||
await page.locator('.c-tree__item__view-control.c-disclosure-triangle').first().click();
|
await page.locator('.c-tree__item__view-control.c-disclosure-triangle').first().click();
|
||||||
// Add the Sine Wave Generator and Clock to the Flexible Layout
|
// Add the Sine Wave Generator and Clock to the Flexible Layout
|
||||||
await sineWaveGeneratorTreeItem.dragTo(page.locator('.c-fl__container.is-empty').first());
|
await sineWaveGeneratorTreeItem.dragTo(page.locator('.c-fl-container.is-empty').first());
|
||||||
await clockTreeItem.dragTo(page.locator('.c-fl__container.is-empty'));
|
await clockTreeItem.dragTo(page.locator('.c-fl-container.is-empty'));
|
||||||
|
|
||||||
// Click on the first frame to select it
|
// Click on the first frame to select it
|
||||||
await page.locator('.c-fl-container__frame').first().click();
|
await page.locator('.c-fl-container__frame').first().click();
|
||||||
@ -122,7 +122,7 @@ test.describe('Flexible Layout', () => {
|
|||||||
expect(await page.locator('.c-fl--rows').count()).toEqual(0);
|
expect(await page.locator('.c-fl--rows').count()).toEqual(0);
|
||||||
|
|
||||||
// Change the layout to rows orientation
|
// Change the layout to rows orientation
|
||||||
await page.getByTitle('Columns layout').click();
|
await page.getByTitle('Switch to rows layout').click();
|
||||||
|
|
||||||
// Assert the layout is in rows orientation
|
// Assert the layout is in rows orientation
|
||||||
expect(await page.locator('.c-fl--rows').count()).toBeGreaterThan(0);
|
expect(await page.locator('.c-fl--rows').count()).toBeGreaterThan(0);
|
||||||
@ -171,7 +171,7 @@ test.describe('Flexible Layout', () => {
|
|||||||
// Expand the 'My Items' folder in the left tree
|
// Expand the 'My Items' folder in the left tree
|
||||||
await page.locator('.c-tree__item__view-control.c-disclosure-triangle').first().click();
|
await page.locator('.c-tree__item__view-control.c-disclosure-triangle').first().click();
|
||||||
// Add the Sine Wave Generator to the Flexible Layout and save changes
|
// Add the Sine Wave Generator to the Flexible Layout and save changes
|
||||||
await sineWaveGeneratorTreeItem.dragTo(page.locator('.c-fl__container.is-empty').first());
|
await sineWaveGeneratorTreeItem.dragTo(page.locator('.c-fl-container.is-empty').first());
|
||||||
await page.locator('button[title="Save"]').click();
|
await page.locator('button[title="Save"]').click();
|
||||||
await page.getByRole('listitem', { name: 'Save and Finish Editing' }).click();
|
await page.getByRole('listitem', { name: 'Save and Finish Editing' }).click();
|
||||||
|
|
||||||
@ -202,7 +202,7 @@ test.describe('Flexible Layout', () => {
|
|||||||
// 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 Flexible Layout and save changes
|
// Add the Sine Wave Generator to the Flexible Layout and save changes
|
||||||
await sineWaveGeneratorTreeItem.dragTo(page.locator('.c-fl__container.is-empty').first());
|
await sineWaveGeneratorTreeItem.dragTo(page.locator('.c-fl-container.is-empty').first());
|
||||||
await page.locator('button[title="Save"]').click();
|
await page.locator('button[title="Save"]').click();
|
||||||
await page.getByRole('listitem', { name: 'Save and Finish Editing' }).click();
|
await page.getByRole('listitem', { name: 'Save and Finish Editing' }).click();
|
||||||
|
|
||||||
@ -242,17 +242,16 @@ test.describe('Flexible Layout', () => {
|
|||||||
name: new RegExp(exampleImageryObject.name)
|
name: new RegExp(exampleImageryObject.name)
|
||||||
});
|
});
|
||||||
// Add the Sine Wave Generator to the Flexible Layout and save changes
|
// Add the Sine Wave Generator to the Flexible Layout and save changes
|
||||||
await exampleImageryTreeItem.dragTo(page.locator('.c-fl__container.is-empty').first());
|
await exampleImageryTreeItem.dragTo(page.locator('.c-fl-container.is-empty').first());
|
||||||
|
|
||||||
await page.locator('button[title="Save"]').click();
|
await page.locator('button[title="Save"]').click();
|
||||||
await page.getByRole('listitem', { name: 'Save and Finish Editing' }).click();
|
await page.getByRole('listitem', { name: 'Save and Finish Editing' }).click();
|
||||||
|
|
||||||
// flip on independent time conductor
|
// flip on independent time conductor
|
||||||
await setIndependentTimeConductorBounds(
|
await setIndependentTimeConductorBounds(page, {
|
||||||
page,
|
start: '2021-12-30 01:01:00.000Z',
|
||||||
'2021-12-30 01:01:00.000Z',
|
end: '2021-12-30 01:11:00.000Z'
|
||||||
'2021-12-30 01:11:00.000Z'
|
});
|
||||||
);
|
|
||||||
|
|
||||||
// check image date
|
// check image date
|
||||||
await expect(page.getByText('2021-12-30 01:11:00.000Z').first()).toBeVisible();
|
await expect(page.getByText('2021-12-30 01:11:00.000Z').first()).toBeVisible();
|
||||||
@ -290,7 +289,7 @@ test.describe('Flexible Layout Toolbar Actions @localStorage', () => {
|
|||||||
await page.getByTitle('Add Container').click();
|
await page.getByTitle('Add Container').click();
|
||||||
expect(await containerHandles.count()).toEqual(3);
|
expect(await containerHandles.count()).toEqual(3);
|
||||||
await page.getByTitle('Remove Container').click();
|
await page.getByTitle('Remove Container').click();
|
||||||
await expect(page.getByRole('dialog', { name: 'Overlay' })).toHaveText(
|
await expect(page.getByRole('dialog', { name: 'Overlay' })).toContainText(
|
||||||
'This action will permanently delete this container from this Flexible Layout. Do you want to continue?'
|
'This action will permanently delete this container from this Flexible Layout. Do you want to continue?'
|
||||||
);
|
);
|
||||||
await page.getByRole('button', { name: 'OK', exact: true }).click();
|
await page.getByRole('button', { name: 'OK', exact: true }).click();
|
||||||
@ -300,7 +299,7 @@ test.describe('Flexible Layout Toolbar Actions @localStorage', () => {
|
|||||||
expect(await page.getByRole('group', { name: 'Frame' }).count()).toEqual(2);
|
expect(await page.getByRole('group', { name: 'Frame' }).count()).toEqual(2);
|
||||||
await page.getByRole('group', { name: 'Child Layout 1' }).click();
|
await page.getByRole('group', { name: 'Child Layout 1' }).click();
|
||||||
await page.getByTitle('Remove Frame').click();
|
await page.getByTitle('Remove Frame').click();
|
||||||
await expect(page.getByRole('dialog', { name: 'Overlay' })).toHaveText(
|
await expect(page.getByRole('dialog', { name: 'Overlay' })).toContainText(
|
||||||
'This action will remove this frame from this Flexible Layout. Do you want to continue?'
|
'This action will remove this frame from this Flexible Layout. Do you want to continue?'
|
||||||
);
|
);
|
||||||
await page.getByRole('button', { name: 'OK', exact: true }).click();
|
await page.getByRole('button', { name: 'OK', exact: true }).click();
|
||||||
@ -310,9 +309,9 @@ test.describe('Flexible Layout Toolbar Actions @localStorage', () => {
|
|||||||
await page.getByRole('columnheader', { name: 'Container Handle 1' }).click();
|
await page.getByRole('columnheader', { name: 'Container Handle 1' }).click();
|
||||||
const flexRows = page.getByLabel('Flexible Layout Row');
|
const flexRows = page.getByLabel('Flexible Layout Row');
|
||||||
expect(await flexRows.count()).toEqual(0);
|
expect(await flexRows.count()).toEqual(0);
|
||||||
await page.getByTitle('Columns layout').click();
|
await page.getByTitle('Switch to rows layout').click();
|
||||||
expect(await flexRows.count()).toEqual(1);
|
expect(await flexRows.count()).toEqual(1);
|
||||||
await page.getByTitle('Rows layout').click();
|
await page.getByTitle('Switch to columns layout').click();
|
||||||
expect(await flexRows.count()).toEqual(0);
|
expect(await flexRows.count()).toEqual(0);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -175,13 +175,13 @@ test.describe('Gauge', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
// Try to create a Folder into the Gauge. Should be disallowed.
|
// Try to create a Folder into the Gauge. Should be disallowed.
|
||||||
await page.getByRole('button', { name: /Create/ }).click();
|
await page.getByRole('button', { name: 'Create' }).click();
|
||||||
await page.getByRole('menuitem', { name: /Folder/ }).click();
|
await page.getByRole('menuitem', { name: /Folder/ }).click();
|
||||||
await expect(page.locator('[aria-label="Save"]')).toBeDisabled();
|
await expect(page.locator('[aria-label="Save"]')).toBeDisabled();
|
||||||
await page.getByLabel('Cancel').click();
|
await page.getByLabel('Cancel').click();
|
||||||
|
|
||||||
// Try to create a Display Layout into the Gauge. Should be disallowed.
|
// Try to create a Display Layout into the Gauge. Should be disallowed.
|
||||||
await page.getByRole('button', { name: /Create/ }).click();
|
await page.getByRole('button', { name: 'Create' }).click();
|
||||||
await page.getByRole('menuitem', { name: /Display Layout/ }).click();
|
await page.getByRole('menuitem', { name: /Display Layout/ }).click();
|
||||||
await expect(page.locator('[aria-label="Save"]')).toBeDisabled();
|
await expect(page.locator('[aria-label="Save"]')).toBeDisabled();
|
||||||
});
|
});
|
||||||
|
@ -24,7 +24,7 @@
|
|||||||
This test suite is dedicated to tests which verify the basic operations surrounding imagery,
|
This test suite is dedicated to tests which verify the basic operations surrounding imagery,
|
||||||
but only assume that example imagery is present.
|
but only assume that example imagery is present.
|
||||||
*/
|
*/
|
||||||
/* globals process */
|
|
||||||
import { createDomainObjectWithDefaults, setRealTimeMode } from '../../../../appActions.js';
|
import { createDomainObjectWithDefaults, setRealTimeMode } from '../../../../appActions.js';
|
||||||
import { waitForAnimations } from '../../../../baseFixtures.js';
|
import { waitForAnimations } from '../../../../baseFixtures.js';
|
||||||
import { expect, test } from '../../../../pluginFixtures.js';
|
import { expect, test } from '../../../../pluginFixtures.js';
|
||||||
@ -773,7 +773,7 @@ async function dragContrastSliderAndAssertFilterValues(page) {
|
|||||||
* Gets the filter:brightness value of the current background-image and
|
* Gets the filter:brightness value of the current background-image and
|
||||||
* asserts against an expected value
|
* asserts against an expected value
|
||||||
* @param {import('@playwright/test').Page} page
|
* @param {import('@playwright/test').Page} page
|
||||||
* @param {String} expected The expected brightness value
|
* @param {string} expected The expected brightness value
|
||||||
*/
|
*/
|
||||||
async function assertBackgroundImageBrightness(page, expected) {
|
async function assertBackgroundImageBrightness(page, expected) {
|
||||||
const backgroundImage = page.locator('.c-imagery__main-image__background-image');
|
const backgroundImage = page.locator('.c-imagery__main-image__background-image');
|
||||||
@ -938,7 +938,7 @@ async function buttonZoomOnImageAndAssert(page) {
|
|||||||
* Gets the filter:contrast value of the current background-image and
|
* Gets the filter:contrast value of the current background-image and
|
||||||
* asserts against an expected value
|
* asserts against an expected value
|
||||||
* @param {import('@playwright/test').Page} page
|
* @param {import('@playwright/test').Page} page
|
||||||
* @param {String} expected The expected contrast value
|
* @param {string} expected The expected contrast value
|
||||||
*/
|
*/
|
||||||
async function assertBackgroundImageContrast(page, expected) {
|
async function assertBackgroundImageContrast(page, expected) {
|
||||||
const backgroundImage = page.locator('.c-imagery__main-image__background-image');
|
const backgroundImage = page.locator('.c-imagery__main-image__background-image');
|
||||||
|
@ -27,7 +27,6 @@ import { expect, test } from '../../../../pluginFixtures.js';
|
|||||||
|
|
||||||
test.describe('Testing numeric data with inspector data visualization (i.e., data pivoting)', () => {
|
test.describe('Testing numeric data with inspector data visualization (i.e., data pivoting)', () => {
|
||||||
test.beforeEach(async ({ page }) => {
|
test.beforeEach(async ({ page }) => {
|
||||||
// eslint-disable-next-line no-undef
|
|
||||||
await page.addInitScript({
|
await page.addInitScript({
|
||||||
path: fileURLToPath(
|
path: fileURLToPath(
|
||||||
new URL('../../../../helper/addInitDataVisualization.js', import.meta.url)
|
new URL('../../../../helper/addInitDataVisualization.js', import.meta.url)
|
||||||
@ -37,6 +36,8 @@ test.describe('Testing numeric data with inspector data visualization (i.e., dat
|
|||||||
});
|
});
|
||||||
|
|
||||||
test('Can click on telemetry and see data in inspector @2p', async ({ page, context }) => {
|
test('Can click on telemetry and see data in inspector @2p', async ({ page, context }) => {
|
||||||
|
const initStartBounds = await page.getByLabel('Start bounds').textContent();
|
||||||
|
const initEndBounds = await page.getByLabel('End bounds').textContent();
|
||||||
const exampleDataVisualizationSource = await createDomainObjectWithDefaults(page, {
|
const exampleDataVisualizationSource = await createDomainObjectWithDefaults(page, {
|
||||||
type: 'Example Data Visualization Source'
|
type: 'Example Data Visualization Source'
|
||||||
});
|
});
|
||||||
@ -78,5 +79,9 @@ test.describe('Testing numeric data with inspector data visualization (i.e., dat
|
|||||||
await newPage.waitForLoadState();
|
await newPage.waitForLoadState();
|
||||||
// expect new tab title to contain 'Second Sine Wave Generator'
|
// expect new tab title to contain 'Second Sine Wave Generator'
|
||||||
await expect(newPage).toHaveTitle('Second Sine Wave Generator');
|
await expect(newPage).toHaveTitle('Second Sine Wave Generator');
|
||||||
|
|
||||||
|
// Verify that "Open in New Tab" preserves the time bounds
|
||||||
|
expect(initStartBounds).toEqual(await newPage.getByLabel('Start bounds').textContent());
|
||||||
|
expect(initEndBounds).toEqual(await newPage.getByLabel('End bounds').textContent());
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
86
e2e/tests/functional/plugins/lad/ladTable.e2e.spec.js
Normal file
@ -0,0 +1,86 @@
|
|||||||
|
/*****************************************************************************
|
||||||
|
* Open MCT, Copyright (c) 2014-2024, United States Government
|
||||||
|
* as represented by the Administrator of the National Aeronautics and Space
|
||||||
|
* Administration. All rights reserved.
|
||||||
|
*
|
||||||
|
* Open MCT is licensed under the Apache License, Version 2.0 (the
|
||||||
|
* "License"); you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0.
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
* License for the specific language governing permissions and limitations
|
||||||
|
* under the License.
|
||||||
|
*
|
||||||
|
* Open MCT includes source code licensed under additional open source
|
||||||
|
* licenses. See the Open Source Licenses file (LICENSES.md) included with
|
||||||
|
* this source code distribution or the Licensing information page available
|
||||||
|
* at runtime from the About dialog for additional information.
|
||||||
|
*****************************************************************************/
|
||||||
|
|
||||||
|
import { createDomainObjectWithDefaults } from '../../../../appActions.js';
|
||||||
|
import { expect, test } from '../../../../pluginFixtures.js';
|
||||||
|
|
||||||
|
test.describe('LAD Table', () => {
|
||||||
|
let ladTable;
|
||||||
|
let swg;
|
||||||
|
|
||||||
|
test.beforeEach(async ({ page }) => {
|
||||||
|
await page.goto('./', { waitUntil: 'domcontentloaded' });
|
||||||
|
|
||||||
|
ladTable = await createDomainObjectWithDefaults(page, {
|
||||||
|
type: 'LAD Table'
|
||||||
|
});
|
||||||
|
|
||||||
|
swg = await createDomainObjectWithDefaults(page, {
|
||||||
|
type: 'Sine Wave Generator',
|
||||||
|
parent: ladTable.uuid
|
||||||
|
});
|
||||||
|
|
||||||
|
await page.goto(ladTable.url);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('Ensure we have numbers in cells', async ({ page }) => {
|
||||||
|
// Wait for the initial value to show after mount
|
||||||
|
await expect(page.getByLabel('lad value').first()).not.toContainText('---');
|
||||||
|
|
||||||
|
const valueFromFirstSineWave = await page.getByLabel('lad value').first().innerText();
|
||||||
|
const firstSineWaveNumber = parseFloat(valueFromFirstSineWave);
|
||||||
|
// ensure we have a float value in the cell and it's finite
|
||||||
|
expect(Number.isFinite(firstSineWaveNumber)).toBeTruthy();
|
||||||
|
|
||||||
|
const valueFromSecondSineWave = await page.getByLabel('lad value').last().innerText();
|
||||||
|
const secondSineWaveNumber = parseFloat(valueFromSecondSineWave);
|
||||||
|
// ensure we have a float value in the cell and it's finite
|
||||||
|
expect(Number.isFinite(secondSineWaveNumber)).toBeTruthy();
|
||||||
|
});
|
||||||
|
|
||||||
|
test(
|
||||||
|
'Can remove telemetry from composition',
|
||||||
|
{
|
||||||
|
annotation: {
|
||||||
|
type: 'issue',
|
||||||
|
description: 'https://github.com/nasa/openmct/issues/7633'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
async ({ page }) => {
|
||||||
|
// Assert that the table is initially populated
|
||||||
|
await expect(page.getByLabel('lad row')).toHaveCount(1);
|
||||||
|
|
||||||
|
// Expand the tree so the SWG is visible
|
||||||
|
await page.getByLabel('Expand My Items').click();
|
||||||
|
await page.getByLabel('Expand LAD Table').click();
|
||||||
|
|
||||||
|
// Right-click the SWG treeitem context menu and click 'Remove' and confirm
|
||||||
|
await page.getByRole('treeitem', { name: swg.name }).click({ button: 'right' });
|
||||||
|
await page.getByRole('menuitem', { name: 'Remove' }).click();
|
||||||
|
await page.getByRole('button', { name: 'OK', exact: true }).click();
|
||||||
|
|
||||||
|
// Assert that the SWG is no longer in the tree and the table is empty
|
||||||
|
await expect(page.getByRole('treeitem', { name: swg.name })).toBeHidden();
|
||||||
|
await expect(page.getByLabel('lad row')).toHaveCount(0);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
});
|
@ -27,6 +27,7 @@ This test suite is dedicated to tests which verify the basic operations surround
|
|||||||
import { fileURLToPath } from 'url';
|
import { fileURLToPath } from 'url';
|
||||||
|
|
||||||
import { createDomainObjectWithDefaults } from '../../../../appActions.js';
|
import { createDomainObjectWithDefaults } from '../../../../appActions.js';
|
||||||
|
import { copy, paste, selectAll } from '../../../../helper/hotkeys/hotkeys.js';
|
||||||
import * as nbUtils from '../../../../helper/notebookUtils.js';
|
import * as nbUtils from '../../../../helper/notebookUtils.js';
|
||||||
import { expect, streamToString, test } from '../../../../pluginFixtures.js';
|
import { expect, streamToString, test } from '../../../../pluginFixtures.js';
|
||||||
|
|
||||||
@ -277,7 +278,6 @@ 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
|
|
||||||
await page.addInitScript({
|
await page.addInitScript({
|
||||||
path: fileURLToPath(new URL('../../../../helper/addInitNotebookWithUrls.js', import.meta.url))
|
path: fileURLToPath(new URL('../../../../helper/addInitNotebookWithUrls.js', import.meta.url))
|
||||||
});
|
});
|
||||||
@ -547,4 +547,53 @@ test.describe('Notebook entry tests', () => {
|
|||||||
);
|
);
|
||||||
await expect(secondLineOfBlockquoteText).toBeVisible();
|
await expect(secondLineOfBlockquoteText).toBeVisible();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Paste into notebook entry tests
|
||||||
|
*/
|
||||||
|
test('Can paste text into a notebook entry', async ({ page }) => {
|
||||||
|
test.info().annotations.push({
|
||||||
|
type: 'issue',
|
||||||
|
description: 'https://github.com/nasa/openmct/issues/7686'
|
||||||
|
});
|
||||||
|
const TEST_TEXT = 'This is a test';
|
||||||
|
const iterations = 20;
|
||||||
|
const EXPECTED_TEXT = TEST_TEXT.repeat(iterations);
|
||||||
|
|
||||||
|
await page.goto(notebookObject.url);
|
||||||
|
|
||||||
|
await nbUtils.addNotebookEntry(page);
|
||||||
|
await nbUtils.enterTextInLastEntry(page, TEST_TEXT);
|
||||||
|
await selectAll(page);
|
||||||
|
await copy(page);
|
||||||
|
for (let i = 0; i < iterations; i++) {
|
||||||
|
await paste(page);
|
||||||
|
}
|
||||||
|
await nbUtils.commitEntry(page);
|
||||||
|
|
||||||
|
await expect(page.locator(`text="${EXPECTED_TEXT}"`)).toBeVisible();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('Prevents pasting text into selected notebook entry if not editing', async ({ page }) => {
|
||||||
|
test.info().annotations.push({
|
||||||
|
type: 'issue',
|
||||||
|
description: 'https://github.com/nasa/openmct/issues/7686'
|
||||||
|
});
|
||||||
|
const TEST_TEXT = 'This is a test';
|
||||||
|
|
||||||
|
await page.goto(notebookObject.url);
|
||||||
|
|
||||||
|
await nbUtils.addNotebookEntry(page);
|
||||||
|
await nbUtils.enterTextInLastEntry(page, TEST_TEXT);
|
||||||
|
await selectAll(page);
|
||||||
|
await copy(page);
|
||||||
|
await paste(page);
|
||||||
|
await nbUtils.commitEntry(page);
|
||||||
|
|
||||||
|
// This should not paste text into the entry
|
||||||
|
await paste(page);
|
||||||
|
|
||||||
|
await expect(await page.locator(`text="${TEST_TEXT.repeat(1)}"`).count()).toEqual(1);
|
||||||
|
await expect(await page.locator(`text="${TEST_TEXT.repeat(2)}"`).count()).toEqual(0);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
@ -71,42 +71,89 @@ 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' });
|
||||||
|
await page.getByLabel('Open the Notebook Snapshot Menu').click();
|
||||||
// Create Notebook
|
|
||||||
// const notebook = await createDomainObjectWithDefaults(page, {
|
|
||||||
// type: 'Notebook',
|
|
||||||
// name: "Test Notebook"
|
|
||||||
// });
|
|
||||||
// // Create Overlay Plot
|
|
||||||
// const snapShotObject = await createDomainObjectWithDefaults(page, {
|
|
||||||
// type: 'Overlay Plot',
|
|
||||||
// name: "Dropped Overlay Plot"
|
|
||||||
// });
|
|
||||||
|
|
||||||
await page.getByLabel('Take a Notebook Snapshot').click();
|
|
||||||
await page.getByRole('menuitem', { name: 'Save to Notebook Snapshots' }).click();
|
await page.getByRole('menuitem', { name: 'Save to Notebook Snapshots' }).click();
|
||||||
await page.getByLabel('Show Snapshots').click();
|
await page.getByLabel('Show Snapshots').click();
|
||||||
});
|
});
|
||||||
test('A snapshot can be Quick Viewed from Container with 3 dot action menu', async ({ page }) => {
|
test('A snapshot can be Quick Viewed from Container with 3 dot action menu', async ({ page }) => {
|
||||||
await page.locator('.c-snapshot.c-ne__embed').first().getByTitle('More actions').click();
|
await page.getByLabel('My Items Notebook Embed').getByLabel('More actions').click();
|
||||||
await page.getByRole('menuitem', { name: 'Quick View' }).click();
|
await page.getByRole('menuitem', { name: 'Quick View' }).click();
|
||||||
await expect(page.locator('.c-overlay__outer')).toBeVisible();
|
await expect(page.getByLabel('Modal Overlay')).toBeVisible();
|
||||||
|
await expect(page.getByLabel('Preview Container')).toBeVisible();
|
||||||
|
});
|
||||||
|
test('A snapshot can be Viewed, Annotated, display deleted, and saved from Container with 3 dot action menu', async ({
|
||||||
|
page
|
||||||
|
}) => {
|
||||||
|
test.info().annotations.push({
|
||||||
|
type: 'issue',
|
||||||
|
description: 'https://github.com/nasa/openmct/issues/7552'
|
||||||
|
});
|
||||||
|
//Open Snapshot Object View
|
||||||
|
await page.getByLabel('My Items Notebook Embed').getByLabel('More actions').click();
|
||||||
|
await page.getByRole('menuitem', { name: 'View Snapshot' }).click();
|
||||||
|
await expect(page.getByRole('dialog', { name: 'Modal Overlay' })).toBeVisible();
|
||||||
|
await expect(page.locator('#snapshotDescriptor')).toHaveText(
|
||||||
|
/SNAPSHOT \d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}/
|
||||||
|
);
|
||||||
|
// Open Annotation Editor with Painterro
|
||||||
|
await page.getByLabel('Annotate this snapshot').click();
|
||||||
|
await expect(page.locator('#snap-annotation-canvas')).toBeVisible();
|
||||||
|
// Clear the canvas
|
||||||
|
await page.getByRole('button', { name: 'Put text [T]' }).click();
|
||||||
|
// Click in the Painterro canvas to add a text annotation
|
||||||
|
await page.locator('.ptro-crp-el').click();
|
||||||
|
await page.locator('.ptro-text-tool-input').fill('...is there life on mars?');
|
||||||
|
// When working with Painterro, we need to check that the Apply button is hidden after clicking
|
||||||
|
await page.getByTitle('Apply').click();
|
||||||
|
await expect(page.getByTitle('Apply')).toBeHidden();
|
||||||
|
|
||||||
|
// Save and exit annotation window
|
||||||
|
await page.getByRole('button', { name: 'Save' }).click();
|
||||||
|
await page.getByRole('button', { name: 'Done' }).click();
|
||||||
|
|
||||||
|
// Open up annotation again
|
||||||
|
await page.getByRole('img', { name: 'My Items thumbnail' }).click();
|
||||||
|
await expect(page.getByLabel('Modal Overlay').getByRole('img')).toBeVisible();
|
||||||
|
});
|
||||||
|
test('A snapshot can be Annotated and saved as a JPG and PNG', async ({ page }) => {
|
||||||
|
//Open Snapshot Object View
|
||||||
|
await page.getByLabel('My Items Notebook Embed').getByLabel('More actions').click();
|
||||||
|
await page.getByRole('menuitem', { name: 'View Snapshot' }).click();
|
||||||
|
await expect(page.getByRole('dialog', { name: 'Modal Overlay' })).toBeVisible();
|
||||||
|
|
||||||
|
// Open Annotation Editor with Painterro
|
||||||
|
await page.getByLabel('Annotate this snapshot').click();
|
||||||
|
await expect(page.locator('#snap-annotation-canvas')).toBeVisible();
|
||||||
|
// Clear the canvas
|
||||||
|
await page.getByRole('button', { name: 'Put text [T]' }).click();
|
||||||
|
// Click in the Painterro canvas to add a text annotation
|
||||||
|
await page.locator('.ptro-crp-el').click();
|
||||||
|
await page.locator('.ptro-text-tool-input').fill('...is there life on mars?');
|
||||||
|
// When working with Painterro, we need to check that the Apply button is hidden after clicking
|
||||||
|
await page.getByTitle('Apply').click();
|
||||||
|
await expect(page.getByTitle('Apply')).toBeHidden();
|
||||||
|
|
||||||
|
// Save and exit annotation window
|
||||||
|
await page.getByRole('button', { name: 'Save' }).click();
|
||||||
|
await page.getByRole('button', { name: 'Done' }).click();
|
||||||
|
|
||||||
|
// Open up annotation again
|
||||||
|
await page.getByRole('img', { name: 'My Items thumbnail' }).click();
|
||||||
|
await expect(page.getByLabel('Modal Overlay').getByRole('img')).toBeVisible();
|
||||||
|
|
||||||
|
// Save as JPG
|
||||||
|
await Promise.all([
|
||||||
|
page.waitForEvent('download'), // Waits for the download event
|
||||||
|
page.getByLabel('Export as JPG').click() // Triggers the download
|
||||||
|
]);
|
||||||
|
|
||||||
|
// Save as PNG
|
||||||
|
await expect(page.getByLabel('Modal Overlay').getByRole('img')).toBeVisible();
|
||||||
|
await Promise.all([
|
||||||
|
page.waitForEvent('download'), // Waits for the download event
|
||||||
|
page.getByLabel('Export as PNG').click() // Triggers the download
|
||||||
|
]);
|
||||||
});
|
});
|
||||||
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 actions').click();
|
|
||||||
await page.getByRole('menuitem', { name: ' View Snapshot' }).click();
|
|
||||||
await expect(page.locator('.c-overlay__outer')).toBeVisible();
|
|
||||||
await page.getByTitle('Annotate').click();
|
|
||||||
await expect(page.locator('#snap-annotation-canvas')).toBeVisible();
|
|
||||||
await page.getByRole('button', { name: '' }).click();
|
|
||||||
// await expect(page.locator('#snap-annotation-canvas')).not.toBeVisible();
|
|
||||||
await page.getByRole('button', { name: 'Save' }).click();
|
|
||||||
await page.getByRole('button', { name: 'Done' }).click();
|
|
||||||
//await expect(await page.locator)
|
|
||||||
}
|
|
||||||
);
|
|
||||||
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',
|
'5 Snapshots can be added to a container and Deleted with Delete All action',
|
||||||
@ -116,10 +163,6 @@ test.describe('Snapshot Container tests', () => {
|
|||||||
'A snapshot can be Deleted from Container with 3 dot action menu',
|
'A snapshot can be Deleted from Container with 3 dot action menu',
|
||||||
async ({ page }) => {}
|
async ({ page }) => {}
|
||||||
);
|
);
|
||||||
test.fixme(
|
|
||||||
'A snapshot can be Navigated To from Container with 3 dot action menu',
|
|
||||||
async ({ page }) => {}
|
|
||||||
);
|
|
||||||
test.fixme(
|
test.fixme(
|
||||||
'A snapshot can be Navigated To Item in Time from Container with 3 dot action menu',
|
'A snapshot can be Navigated To Item in Time from Container with 3 dot action menu',
|
||||||
async ({ page }) => {}
|
async ({ page }) => {}
|
||||||
@ -151,11 +194,4 @@ test.describe('Snapshot Container tests', () => {
|
|||||||
//Snapshot removed from container?
|
//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
|
|
||||||
}
|
|
||||||
);
|
|
||||||
});
|
});
|
||||||
|
Before Width: | Height: | Size: 18 KiB |
Before Width: | Height: | Size: 19 KiB |
Before Width: | Height: | Size: 21 KiB |
Before Width: | Height: | Size: 19 KiB |
@ -24,138 +24,60 @@
|
|||||||
Tests to verify log plot functionality when objects are missing
|
Tests to verify log plot functionality when objects are missing
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
import { createDomainObjectWithDefaults } from '../../../../appActions.js';
|
||||||
import { expect, test } from '../../../../pluginFixtures.js';
|
import { expect, test } from '../../../../pluginFixtures.js';
|
||||||
|
|
||||||
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 ({
|
test.beforeEach(async ({ page }) => {
|
||||||
page,
|
await page.goto('./', { waitUntil: 'domcontentloaded' });
|
||||||
browserName,
|
});
|
||||||
openmctConfig
|
test('Displays empty div for missing stacked plot item', async ({ page, browserName }) => {
|
||||||
}) => {
|
|
||||||
// eslint-disable-next-line playwright/no-skipped-test
|
// eslint-disable-next-line playwright/no-skipped-test
|
||||||
test.skip(browserName === 'firefox', 'Firefox failing due to console events being missed');
|
test.skip(browserName === 'firefox', 'Firefox failing due to console events being missed');
|
||||||
|
|
||||||
const { myItemsFolderName } = openmctConfig;
|
let warningReceived = false;
|
||||||
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());
|
warningReceived = true;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
//Make stacked plot
|
const stackedPlot = await createDomainObjectWithDefaults(page, {
|
||||||
await makeStackedPlot(page, myItemsFolderName);
|
type: 'Stacked Plot'
|
||||||
|
});
|
||||||
|
await createDomainObjectWithDefaults(page, {
|
||||||
|
type: 'Sine Wave Generator',
|
||||||
|
parent: stackedPlot.uuid
|
||||||
|
});
|
||||||
|
await createDomainObjectWithDefaults(page, {
|
||||||
|
type: 'Sine Wave Generator',
|
||||||
|
parent: stackedPlot.uuid
|
||||||
|
});
|
||||||
|
|
||||||
//Gets local storage and deletes the last sine wave generator in the stacked plot
|
//Gets local storage and deletes the last sine wave generator in the stacked plot
|
||||||
const localStorage = await page.evaluate(() => window.localStorage);
|
const mct = await page.evaluate(() => window.localStorage.getItem('mct'));
|
||||||
const parsedData = JSON.parse(localStorage.mct);
|
const parsedData = JSON.parse(mct);
|
||||||
const keys = Object.keys(parsedData);
|
const key = Object.entries(parsedData).find(([, value]) => value.type === 'generator')?.[0];
|
||||||
const lastKey = keys[keys.length - 1];
|
|
||||||
|
|
||||||
delete parsedData[lastKey];
|
delete parsedData[key];
|
||||||
|
|
||||||
//Sets local storage with missing object
|
//Sets local storage with missing object
|
||||||
await page.evaluate(`window.localStorage.setItem('mct', '${JSON.stringify(parsedData)}')`);
|
const jsonData = JSON.stringify(parsedData);
|
||||||
|
await page.evaluate((data) => {
|
||||||
|
window.localStorage.setItem('mct', data);
|
||||||
|
}, jsonData);
|
||||||
|
|
||||||
//Reloads page and clicks on stacked plot
|
//Reloads page and clicks on stacked plot
|
||||||
await Promise.all([page.reload(), page.waitForLoadState('networkidle')]);
|
await page.reload({ waitUntil: 'domcontentloaded' });
|
||||||
|
await page.goto(stackedPlot.url);
|
||||||
|
|
||||||
//Verify Main section is there on load
|
//Verify Main section is there on load
|
||||||
await expect
|
await expect(page.locator('.l-browse-bar__object-name')).toContainText(stackedPlot.name);
|
||||||
.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
|
//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);
|
await expect(page.getByLabel('Stacked Plot Item')).toHaveCount(1);
|
||||||
//Verify that console.warn is thrown
|
//Verify that console.warn was thrown
|
||||||
expect(errorLogs).toHaveLength(1);
|
expect(warningReceived).toBe(true);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
/**
|
|
||||||
* This is used the create a stacked plot object
|
|
||||||
* @private
|
|
||||||
*/
|
|
||||||
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
|
|
||||||
await page.goto('./', { waitUntil: 'domcontentloaded' });
|
|
||||||
|
|
||||||
// create stacked plot
|
|
||||||
await page.locator('button.c-create-button').click();
|
|
||||||
await page.locator('li[role="menuitem"]:has-text("Stacked Plot")').click();
|
|
||||||
|
|
||||||
await Promise.all([
|
|
||||||
page.waitForNavigation({ waitUntil: 'networkidle' }),
|
|
||||||
page.locator('button:has-text("OK")').click(),
|
|
||||||
//Wait for Save Banner to appear
|
|
||||||
page.waitForSelector('.c-message-banner__message')
|
|
||||||
]);
|
|
||||||
|
|
||||||
// save the stacked plot
|
|
||||||
await saveStackedPlot(page);
|
|
||||||
|
|
||||||
// create a sinewave generator
|
|
||||||
await createSineWaveGenerator(page);
|
|
||||||
|
|
||||||
// click on 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()
|
|
||||||
]);
|
|
||||||
|
|
||||||
// create a second sinewave generator
|
|
||||||
await createSineWaveGenerator(page);
|
|
||||||
|
|
||||||
// click on 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()
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This is used to save a stacked plot object
|
|
||||||
* @private
|
|
||||||
*/
|
|
||||||
async function saveStackedPlot(page) {
|
|
||||||
// save stacked plot
|
|
||||||
await page
|
|
||||||
.locator('text=Snapshot Save and Finish Editing Save and Continue Editing >> button')
|
|
||||||
.nth(1)
|
|
||||||
.click();
|
|
||||||
|
|
||||||
await Promise.all([
|
|
||||||
page.locator('text=Save and Finish Editing').click(),
|
|
||||||
//Wait for Save Banner to appear
|
|
||||||
page.waitForSelector('.c-message-banner__message')
|
|
||||||
]);
|
|
||||||
//Wait until Save Banner is gone
|
|
||||||
await page.locator('.c-message-banner__close-button').click();
|
|
||||||
await page.waitForSelector('.c-message-banner__message', { state: 'detached' });
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This is used to create a sine wave generator object
|
|
||||||
* @private
|
|
||||||
*/
|
|
||||||
async function createSineWaveGenerator(page) {
|
|
||||||
//Create sine wave generator
|
|
||||||
await page.locator('button.c-create-button').click();
|
|
||||||
await page.locator('li[role="menuitem"]:has-text("Sine Wave Generator")').click();
|
|
||||||
|
|
||||||
await Promise.all([
|
|
||||||
page.waitForNavigation({ waitUntil: 'networkidle' }),
|
|
||||||
page.locator('button:has-text("OK")').click(),
|
|
||||||
//Wait for Save Banner to appear
|
|
||||||
page.waitForSelector('.c-message-banner__message')
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
|
@ -54,7 +54,9 @@ test.describe('Plots work in Previews', () => {
|
|||||||
await page.getByRole('listitem', { name: 'Save and Finish Editing' }).click();
|
await page.getByRole('listitem', { name: 'Save and Finish Editing' }).click();
|
||||||
|
|
||||||
// right click on the plot and select view large
|
// right click on the plot and select view large
|
||||||
await page.getByLabel('Sine', { exact: true }).click({ button: 'right' });
|
await page.getByLabel(/Alpha-numeric telemetry value of.*/).click({
|
||||||
|
button: 'right'
|
||||||
|
});
|
||||||
await page.getByLabel('View Historical Data').click();
|
await page.getByLabel('View Historical Data').click();
|
||||||
await expect(page.getByLabel('Preview Container').getByLabel('Plot Canvas')).toBeVisible();
|
await expect(page.getByLabel('Preview Container').getByLabel('Plot Canvas')).toBeVisible();
|
||||||
await page.getByRole('button', { name: 'Close' }).click();
|
await page.getByRole('button', { name: 'Close' }).click();
|
||||||
|
@ -122,4 +122,14 @@ test.describe('Reload action', () => {
|
|||||||
expect(fullReloadAlphaTelemetryValue).not.toEqual(afterReloadAlphaTelemetryValue);
|
expect(fullReloadAlphaTelemetryValue).not.toEqual(afterReloadAlphaTelemetryValue);
|
||||||
expect(fullReloadBetaTelemetryValue).not.toEqual(afterReloadBetaTelemetryValue);
|
expect(fullReloadBetaTelemetryValue).not.toEqual(afterReloadBetaTelemetryValue);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('is disabled in Previews', async ({ page }) => {
|
||||||
|
test.info().annotations.push({
|
||||||
|
type: 'issue',
|
||||||
|
description: 'https://github.com/nasa/openmct/issues/7638'
|
||||||
|
});
|
||||||
|
await page.getByLabel('Alpha Table Frame Controls').getByLabel('Large View').click();
|
||||||
|
await page.getByLabel('Modal Overlay').getByLabel('More actions').click();
|
||||||
|
await expect(page.getByLabel('Reload')).toBeHidden();
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
@ -114,7 +114,9 @@ test.describe('Flexible Layout styling', () => {
|
|||||||
hexToRGB(defaultFrameBorderColor),
|
hexToRGB(defaultFrameBorderColor),
|
||||||
NO_STYLE_RGBA,
|
NO_STYLE_RGBA,
|
||||||
hexToRGB(setTextColor),
|
hexToRGB(setTextColor),
|
||||||
page.getByLabel('StackedPlot1 Frame').getByLabel('Stacked Plot Style Target')
|
page
|
||||||
|
.getByRole('group', { name: 'StackedPlot1 Frame' })
|
||||||
|
.getByLabel('Stacked Plot Style Target')
|
||||||
);
|
);
|
||||||
|
|
||||||
// Check styles on StackedPlot2. Note: https://github.com/nasa/openmct/issues/7337
|
// Check styles on StackedPlot2. Note: https://github.com/nasa/openmct/issues/7337
|
||||||
@ -122,7 +124,9 @@ test.describe('Flexible Layout styling', () => {
|
|||||||
hexToRGB(defaultFrameBorderColor),
|
hexToRGB(defaultFrameBorderColor),
|
||||||
NO_STYLE_RGBA,
|
NO_STYLE_RGBA,
|
||||||
hexToRGB(setTextColor),
|
hexToRGB(setTextColor),
|
||||||
page.getByLabel('StackedPlot2 Frame').getByLabel('Stacked Plot Style Target')
|
page
|
||||||
|
.getByRole('group', { name: 'StackedPlot2 Frame' })
|
||||||
|
.getByLabel('Stacked Plot Style Target')
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -143,7 +147,9 @@ test.describe('Flexible Layout styling', () => {
|
|||||||
hexToRGB(defaultBorderTargetColor),
|
hexToRGB(defaultBorderTargetColor),
|
||||||
NO_STYLE_RGBA,
|
NO_STYLE_RGBA,
|
||||||
hexToRGB(defaultTextColor),
|
hexToRGB(defaultTextColor),
|
||||||
page.getByLabel('StackedPlot1 Frame').getByLabel('Stacked Plot Style Target')
|
page
|
||||||
|
.getByRole('group', { name: 'StackedPlot1 Frame' })
|
||||||
|
.getByLabel('Stacked Plot Style Target')
|
||||||
);
|
);
|
||||||
|
|
||||||
// Check styles on StackedPlot2
|
// Check styles on StackedPlot2
|
||||||
@ -151,7 +157,9 @@ test.describe('Flexible Layout styling', () => {
|
|||||||
hexToRGB(defaultBorderTargetColor),
|
hexToRGB(defaultBorderTargetColor),
|
||||||
NO_STYLE_RGBA,
|
NO_STYLE_RGBA,
|
||||||
hexToRGB(defaultTextColor),
|
hexToRGB(defaultTextColor),
|
||||||
page.getByLabel('StackedPlot2 Frame').getByLabel('Stacked Plot Style Target')
|
page
|
||||||
|
.getByRole('group', { name: 'StackedPlot2 Frame' })
|
||||||
|
.getByLabel('Stacked Plot Style Target')
|
||||||
);
|
);
|
||||||
|
|
||||||
// Set styles using setStyles function on StackedPlot1 but not StackedPlot2
|
// Set styles using setStyles function on StackedPlot1 but not StackedPlot2
|
||||||
@ -160,7 +168,7 @@ test.describe('Flexible Layout styling', () => {
|
|||||||
setBorderColor,
|
setBorderColor,
|
||||||
setBackgroundColor,
|
setBackgroundColor,
|
||||||
setTextColor,
|
setTextColor,
|
||||||
page.getByLabel('StackedPlot1 Frame')
|
page.getByRole('group', { name: 'StackedPlot1 Frame' })
|
||||||
);
|
);
|
||||||
|
|
||||||
// Check styles on StackedPlot1
|
// Check styles on StackedPlot1
|
||||||
@ -168,7 +176,9 @@ test.describe('Flexible Layout styling', () => {
|
|||||||
hexToRGB(setBorderColor),
|
hexToRGB(setBorderColor),
|
||||||
hexToRGB(setBackgroundColor),
|
hexToRGB(setBackgroundColor),
|
||||||
hexToRGB(setTextColor),
|
hexToRGB(setTextColor),
|
||||||
page.getByLabel('StackedPlot1 Frame').getByLabel('Stacked Plot Style Target')
|
page
|
||||||
|
.getByRole('group', { name: 'StackedPlot1 Frame' })
|
||||||
|
.getByLabel('Stacked Plot Style Target')
|
||||||
);
|
);
|
||||||
|
|
||||||
// Check styles on StackedPlot2
|
// Check styles on StackedPlot2
|
||||||
@ -176,7 +186,9 @@ test.describe('Flexible Layout styling', () => {
|
|||||||
hexToRGB(defaultBorderTargetColor),
|
hexToRGB(defaultBorderTargetColor),
|
||||||
NO_STYLE_RGBA,
|
NO_STYLE_RGBA,
|
||||||
hexToRGB(defaultTextColor),
|
hexToRGB(defaultTextColor),
|
||||||
page.getByLabel('StackedPlot2 Frame').getByLabel('Stacked Plot Style Target')
|
page
|
||||||
|
.getByRole('group', { name: 'StackedPlot2 Frame' })
|
||||||
|
.getByLabel('Stacked Plot Style Target')
|
||||||
);
|
);
|
||||||
|
|
||||||
// Save Flexible Layout
|
// Save Flexible Layout
|
||||||
@ -191,7 +203,9 @@ test.describe('Flexible Layout styling', () => {
|
|||||||
hexToRGB(setBorderColor),
|
hexToRGB(setBorderColor),
|
||||||
hexToRGB(setBackgroundColor),
|
hexToRGB(setBackgroundColor),
|
||||||
hexToRGB(setTextColor),
|
hexToRGB(setTextColor),
|
||||||
page.getByLabel('StackedPlot1 Frame').getByLabel('Stacked Plot Style Target')
|
page
|
||||||
|
.getByRole('group', { name: 'StackedPlot1 Frame' })
|
||||||
|
.getByLabel('Stacked Plot Style Target')
|
||||||
);
|
);
|
||||||
|
|
||||||
// Check styles on StackedPlot2
|
// Check styles on StackedPlot2
|
||||||
@ -199,7 +213,9 @@ test.describe('Flexible Layout styling', () => {
|
|||||||
hexToRGB(defaultBorderTargetColor),
|
hexToRGB(defaultBorderTargetColor),
|
||||||
NO_STYLE_RGBA,
|
NO_STYLE_RGBA,
|
||||||
hexToRGB(defaultTextColor),
|
hexToRGB(defaultTextColor),
|
||||||
page.getByLabel('StackedPlot2 Frame').getByLabel('Stacked Plot Style Target')
|
page
|
||||||
|
.getByRole('group', { name: 'StackedPlot2 Frame' })
|
||||||
|
.getByLabel('Stacked Plot Style Target')
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -241,7 +257,9 @@ test.describe('Flexible Layout styling', () => {
|
|||||||
hexToRGB(setBorderColor),
|
hexToRGB(setBorderColor),
|
||||||
hexToRGB(setBackgroundColor),
|
hexToRGB(setBackgroundColor),
|
||||||
hexToRGB(setTextColor),
|
hexToRGB(setTextColor),
|
||||||
page.getByLabel('StackedPlot1 Frame').getByLabel('Stacked Plot Style Target')
|
page
|
||||||
|
.getByRole('group', { name: 'StackedPlot1 Frame' })
|
||||||
|
.getByLabel('Stacked Plot Style Target')
|
||||||
);
|
);
|
||||||
|
|
||||||
// Check styles on StackedPlot2 to verify they are the default
|
// Check styles on StackedPlot2 to verify they are the default
|
||||||
@ -249,7 +267,9 @@ test.describe('Flexible Layout styling', () => {
|
|||||||
hexToRGB(defaultBorderTargetColor),
|
hexToRGB(defaultBorderTargetColor),
|
||||||
NO_STYLE_RGBA,
|
NO_STYLE_RGBA,
|
||||||
hexToRGB(defaultTextColor),
|
hexToRGB(defaultTextColor),
|
||||||
page.getByLabel('StackedPlot2 Frame').getByLabel('Stacked Plot Style Target')
|
page
|
||||||
|
.getByRole('group', { name: 'StackedPlot2 Frame' })
|
||||||
|
.getByLabel('Stacked Plot Style Target')
|
||||||
);
|
);
|
||||||
|
|
||||||
// Set styles using setStyles function on StackedPlot2
|
// Set styles using setStyles function on StackedPlot2
|
||||||
@ -258,7 +278,7 @@ test.describe('Flexible Layout styling', () => {
|
|||||||
setBorderColor,
|
setBorderColor,
|
||||||
setBackgroundColor,
|
setBackgroundColor,
|
||||||
setTextColor,
|
setTextColor,
|
||||||
page.getByLabel('StackedPlot2 Frame')
|
page.getByRole('group', { name: 'StackedPlot2 Frame' })
|
||||||
);
|
);
|
||||||
|
|
||||||
// Check styles on StackedPlot2
|
// Check styles on StackedPlot2
|
||||||
@ -266,7 +286,9 @@ test.describe('Flexible Layout styling', () => {
|
|||||||
hexToRGB(setBorderColor),
|
hexToRGB(setBorderColor),
|
||||||
hexToRGB(setBackgroundColor),
|
hexToRGB(setBackgroundColor),
|
||||||
hexToRGB(setTextColor),
|
hexToRGB(setTextColor),
|
||||||
page.getByLabel('StackedPlot2 Frame').getByLabel('Stacked Plot Style Target')
|
page
|
||||||
|
.getByRole('group', { name: 'StackedPlot2 Frame' })
|
||||||
|
.getByLabel('Stacked Plot Style Target')
|
||||||
);
|
);
|
||||||
|
|
||||||
// Save Flexible Layout
|
// Save Flexible Layout
|
||||||
@ -281,7 +303,9 @@ test.describe('Flexible Layout styling', () => {
|
|||||||
hexToRGB(setBorderColor),
|
hexToRGB(setBorderColor),
|
||||||
hexToRGB(setBackgroundColor),
|
hexToRGB(setBackgroundColor),
|
||||||
hexToRGB(setTextColor),
|
hexToRGB(setTextColor),
|
||||||
page.getByLabel('StackedPlot1 Frame').getByLabel('Stacked Plot Style Target')
|
page
|
||||||
|
.getByRole('group', { name: 'StackedPlot1 Frame' })
|
||||||
|
.getByLabel('Stacked Plot Style Target')
|
||||||
);
|
);
|
||||||
|
|
||||||
// Check styles on StackedPlot2
|
// Check styles on StackedPlot2
|
||||||
@ -289,7 +313,9 @@ test.describe('Flexible Layout styling', () => {
|
|||||||
hexToRGB(setBorderColor),
|
hexToRGB(setBorderColor),
|
||||||
hexToRGB(setBackgroundColor),
|
hexToRGB(setBackgroundColor),
|
||||||
hexToRGB(setTextColor),
|
hexToRGB(setTextColor),
|
||||||
page.getByLabel('StackedPlot2 Frame').getByLabel('Stacked Plot Style Target')
|
page
|
||||||
|
.getByRole('group', { name: 'StackedPlot2 Frame' })
|
||||||
|
.getByLabel('Stacked Plot Style Target')
|
||||||
);
|
);
|
||||||
|
|
||||||
// Directly navigate to the flexible layout
|
// Directly navigate to the flexible layout
|
||||||
@ -326,7 +352,9 @@ test.describe('Flexible Layout styling', () => {
|
|||||||
hexToRGB(setBorderColor),
|
hexToRGB(setBorderColor),
|
||||||
hexToRGB(setBackgroundColor),
|
hexToRGB(setBackgroundColor),
|
||||||
hexToRGB(setTextColor),
|
hexToRGB(setTextColor),
|
||||||
page.getByLabel('StackedPlot1 Frame').getByLabel('Stacked Plot Style Target')
|
page
|
||||||
|
.getByRole('group', { name: 'StackedPlot1 Frame' })
|
||||||
|
.getByLabel('Stacked Plot Style Target')
|
||||||
);
|
);
|
||||||
|
|
||||||
// Check styles on StackedPlot2 matches previous set colors
|
// Check styles on StackedPlot2 matches previous set colors
|
||||||
@ -334,7 +362,9 @@ test.describe('Flexible Layout styling', () => {
|
|||||||
hexToRGB(setBorderColor),
|
hexToRGB(setBorderColor),
|
||||||
hexToRGB(setBackgroundColor),
|
hexToRGB(setBackgroundColor),
|
||||||
hexToRGB(setTextColor),
|
hexToRGB(setTextColor),
|
||||||
page.getByLabel('StackedPlot2 Frame').getByLabel('Stacked Plot Style Target')
|
page
|
||||||
|
.getByRole('group', { name: 'StackedPlot2 Frame' })
|
||||||
|
.getByLabel('Stacked Plot Style Target')
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -356,7 +386,7 @@ test.describe('Flexible Layout styling', () => {
|
|||||||
setBorderColor,
|
setBorderColor,
|
||||||
setBackgroundColor,
|
setBackgroundColor,
|
||||||
setTextColor,
|
setTextColor,
|
||||||
page.getByLabel('StackedPlot1 Frame')
|
page.getByRole('group', { name: 'StackedPlot1 Frame' })
|
||||||
);
|
);
|
||||||
|
|
||||||
// Check styles using checkStyles function
|
// Check styles using checkStyles function
|
||||||
@ -364,7 +394,9 @@ test.describe('Flexible Layout styling', () => {
|
|||||||
hexToRGB(setBorderColor),
|
hexToRGB(setBorderColor),
|
||||||
hexToRGB(setBackgroundColor),
|
hexToRGB(setBackgroundColor),
|
||||||
hexToRGB(setTextColor),
|
hexToRGB(setTextColor),
|
||||||
page.getByLabel('StackedPlot1 Frame').getByLabel('Stacked Plot Style Target')
|
page
|
||||||
|
.getByRole('group', { name: 'StackedPlot1 Frame' })
|
||||||
|
.getByLabel('Stacked Plot Style Target')
|
||||||
);
|
);
|
||||||
|
|
||||||
// Save Flexible Layout
|
// Save Flexible Layout
|
||||||
@ -386,7 +418,7 @@ test.describe('Flexible Layout styling', () => {
|
|||||||
'No Style',
|
'No Style',
|
||||||
'No Style',
|
'No Style',
|
||||||
'No Style',
|
'No Style',
|
||||||
page.getByLabel('StackedPlot1 Frame')
|
page.getByRole('group', { name: 'StackedPlot1 Frame' })
|
||||||
);
|
);
|
||||||
|
|
||||||
// Check styles using checkStyles function
|
// Check styles using checkStyles function
|
||||||
@ -394,7 +426,9 @@ test.describe('Flexible Layout styling', () => {
|
|||||||
hexToRGB(defaultBorderTargetColor),
|
hexToRGB(defaultBorderTargetColor),
|
||||||
NO_STYLE_RGBA,
|
NO_STYLE_RGBA,
|
||||||
hexToRGB(inheritedColor),
|
hexToRGB(inheritedColor),
|
||||||
page.getByLabel('StackedPlot1 Frame').getByLabel('Stacked Plot Style Target')
|
page
|
||||||
|
.getByRole('group', { name: 'StackedPlot1 Frame' })
|
||||||
|
.getByLabel('Stacked Plot Style Target')
|
||||||
);
|
);
|
||||||
// Save Flexible Layout
|
// Save Flexible Layout
|
||||||
await page.getByRole('button', { name: 'Save' }).click();
|
await page.getByRole('button', { name: 'Save' }).click();
|
||||||
@ -408,7 +442,9 @@ test.describe('Flexible Layout styling', () => {
|
|||||||
hexToRGB(defaultBorderTargetColor),
|
hexToRGB(defaultBorderTargetColor),
|
||||||
NO_STYLE_RGBA,
|
NO_STYLE_RGBA,
|
||||||
hexToRGB(inheritedColor),
|
hexToRGB(inheritedColor),
|
||||||
page.getByLabel('StackedPlot1 Frame').getByLabel('Stacked Plot Style Target')
|
page
|
||||||
|
.getByRole('group', { name: 'StackedPlot1 Frame' })
|
||||||
|
.getByLabel('Stacked Plot Style Target')
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -67,7 +67,7 @@ test.describe('Style Inspector Options', () => {
|
|||||||
await expect(page.getByRole('tab', { name: 'Styles' })).toBeVisible();
|
await expect(page.getByRole('tab', { name: 'Styles' })).toBeVisible();
|
||||||
|
|
||||||
// Select Stacked Layout Column
|
// Select Stacked Layout Column
|
||||||
await page.getByLabel('Stacked Plot Frame').click();
|
await page.getByRole('group', { name: 'Stacked Plot Frame' }).click();
|
||||||
|
|
||||||
// The overall Flex Layout or Stacked Plot itself MUST be style-able.
|
// The overall Flex Layout or Stacked Plot itself MUST be style-able.
|
||||||
await expect(page.getByRole('tab', { name: 'Styles' })).toBeVisible();
|
await expect(page.getByRole('tab', { name: 'Styles' })).toBeVisible();
|
||||||
|
@ -69,5 +69,9 @@ test.describe('Preview mode', () => {
|
|||||||
await page.getByLabel('Overlay').getByLabel('More actions').click();
|
await page.getByLabel('Overlay').getByLabel('More actions').click();
|
||||||
await expect(page.getByLabel('Export Table Data')).toBeVisible();
|
await expect(page.getByLabel('Export Table Data')).toBeVisible();
|
||||||
await expect(page.getByLabel('Export Marked Rows')).toBeVisible();
|
await expect(page.getByLabel('Export Marked Rows')).toBeVisible();
|
||||||
|
await expect(page.getByLabel('Export Marked Rows')).toBeDisabled();
|
||||||
|
await page.getByLabel('Pause').click();
|
||||||
|
const tableWrapper = page.getByLabel('Preview Container').locator('div.c-table-wrapper');
|
||||||
|
await expect(tableWrapper).toHaveClass(/is-paused/);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -20,10 +20,71 @@
|
|||||||
* at runtime from the About dialog for additional information.
|
* at runtime from the About dialog for additional information.
|
||||||
*****************************************************************************/
|
*****************************************************************************/
|
||||||
|
|
||||||
import { createDomainObjectWithDefaults, setTimeConductorBounds } from '../../../../appActions.js';
|
import {
|
||||||
|
createDomainObjectWithDefaults,
|
||||||
|
navigateToObjectWithRealTime,
|
||||||
|
setTimeConductorBounds
|
||||||
|
} from '../../../../appActions.js';
|
||||||
import { expect, test } from '../../../../pluginFixtures.js';
|
import { expect, test } from '../../../../pluginFixtures.js';
|
||||||
|
|
||||||
test.describe('Telemetry Table', () => {
|
test.describe('Telemetry Table', () => {
|
||||||
|
let table;
|
||||||
|
test.beforeEach(async ({ page }) => {
|
||||||
|
await page.goto('./', { waitUntil: 'domcontentloaded' });
|
||||||
|
table = await createDomainObjectWithDefaults(page, { type: 'Telemetry Table' });
|
||||||
|
});
|
||||||
|
|
||||||
|
test('Limits to 50 rows by default', async ({ page }) => {
|
||||||
|
await createDomainObjectWithDefaults(page, {
|
||||||
|
type: 'Sine Wave Generator',
|
||||||
|
parent: table.uuid
|
||||||
|
});
|
||||||
|
await navigateToObjectWithRealTime(page, table.url);
|
||||||
|
const rows = page.getByLabel('table content').getByLabel('Table Row');
|
||||||
|
await expect(rows).toHaveCount(50);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('on load, auto scrolls to top for descending, and to bottom for ascending', async ({
|
||||||
|
page
|
||||||
|
}) => {
|
||||||
|
const sineWaveGenerator = await createDomainObjectWithDefaults(page, {
|
||||||
|
type: 'Sine Wave Generator',
|
||||||
|
parent: table.uuid
|
||||||
|
});
|
||||||
|
|
||||||
|
// verify in telemetry table object view
|
||||||
|
await navigateToObjectWithRealTime(page, table.url);
|
||||||
|
|
||||||
|
expect(await getScrollPosition(page)).toBe(0);
|
||||||
|
|
||||||
|
// verify in telemetry table view
|
||||||
|
await page.goto(sineWaveGenerator.url);
|
||||||
|
await page.getByLabel('Open the View Switcher Menu').click();
|
||||||
|
await page.getByText('Telemetry Table', { exact: true }).click();
|
||||||
|
|
||||||
|
expect(await getScrollPosition(page)).toBe(0);
|
||||||
|
|
||||||
|
// navigate back to table
|
||||||
|
await page.goto(table.url);
|
||||||
|
|
||||||
|
// go into edit mode
|
||||||
|
await page.getByLabel('Edit Object').click();
|
||||||
|
|
||||||
|
// change sort direction
|
||||||
|
await page.locator('thead div').filter({ hasText: 'Time' }).click();
|
||||||
|
|
||||||
|
// save view
|
||||||
|
await page.getByRole('button', { name: 'Save' }).click();
|
||||||
|
await page.getByRole('listitem', { name: 'Save and Finish Editing' }).click();
|
||||||
|
|
||||||
|
// navigate away and back
|
||||||
|
await page.goto(sineWaveGenerator.url);
|
||||||
|
await page.goto(table.url);
|
||||||
|
|
||||||
|
// verify scroll position
|
||||||
|
expect(await getScrollPosition(page, false)).toBeLessThan(1);
|
||||||
|
});
|
||||||
|
|
||||||
test('unpauses and filters data when paused by button and user changes bounds', async ({
|
test('unpauses and filters data when paused by button and user changes bounds', async ({
|
||||||
page
|
page
|
||||||
}) => {
|
}) => {
|
||||||
@ -34,7 +95,6 @@ test.describe('Telemetry Table', () => {
|
|||||||
|
|
||||||
await page.goto('./', { waitUntil: 'domcontentloaded' });
|
await page.goto('./', { waitUntil: 'domcontentloaded' });
|
||||||
|
|
||||||
const table = await createDomainObjectWithDefaults(page, { type: 'Telemetry Table' });
|
|
||||||
await createDomainObjectWithDefaults(page, {
|
await createDomainObjectWithDefaults(page, {
|
||||||
type: 'Sine Wave Generator',
|
type: 'Sine Wave Generator',
|
||||||
parent: table.uuid
|
parent: table.uuid
|
||||||
@ -78,7 +138,6 @@ test.describe('Telemetry Table', () => {
|
|||||||
test('Supports filtering telemetry by regular text search', async ({ page }) => {
|
test('Supports filtering telemetry by regular text search', async ({ page }) => {
|
||||||
await page.goto('./', { waitUntil: 'domcontentloaded' });
|
await page.goto('./', { waitUntil: 'domcontentloaded' });
|
||||||
|
|
||||||
const table = await createDomainObjectWithDefaults(page, { type: 'Telemetry Table' });
|
|
||||||
await createDomainObjectWithDefaults(page, {
|
await createDomainObjectWithDefaults(page, {
|
||||||
type: 'Event Message Generator',
|
type: 'Event Message Generator',
|
||||||
parent: table.uuid
|
parent: table.uuid
|
||||||
@ -121,7 +180,6 @@ test.describe('Telemetry Table', () => {
|
|||||||
test('Supports filtering using Regex', async ({ page }) => {
|
test('Supports filtering using Regex', async ({ page }) => {
|
||||||
await page.goto('./', { waitUntil: 'domcontentloaded' });
|
await page.goto('./', { waitUntil: 'domcontentloaded' });
|
||||||
|
|
||||||
const table = await createDomainObjectWithDefaults(page, { type: 'Telemetry Table' });
|
|
||||||
await createDomainObjectWithDefaults(page, {
|
await createDomainObjectWithDefaults(page, {
|
||||||
type: 'Event Message Generator',
|
type: 'Event Message Generator',
|
||||||
parent: table.uuid
|
parent: table.uuid
|
||||||
@ -165,3 +223,42 @@ test.describe('Telemetry Table', () => {
|
|||||||
await page.click('button[title="Pause"]');
|
await page.click('button[title="Pause"]');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
async function getScrollPosition(page, top = true) {
|
||||||
|
const tableBody = page.locator('.c-table__body-w');
|
||||||
|
|
||||||
|
// Wait for the scrollbar to appear
|
||||||
|
await tableBody.evaluate((node) => {
|
||||||
|
return new Promise((resolve) => {
|
||||||
|
function checkScroll() {
|
||||||
|
if (node.scrollHeight > node.clientHeight) {
|
||||||
|
resolve();
|
||||||
|
} else {
|
||||||
|
setTimeout(checkScroll, 100);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
checkScroll();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// make sure there are rows
|
||||||
|
const rows = page.getByLabel('table content').getByLabel('Table Row');
|
||||||
|
await rows.first().waitFor();
|
||||||
|
|
||||||
|
// Using this to allow for rows to come and go, so we can truly test the scroll position
|
||||||
|
// eslint-disable-next-line playwright/no-wait-for-timeout
|
||||||
|
await page.waitForTimeout(1000);
|
||||||
|
|
||||||
|
const { scrollTop, clientHeight, scrollHeight } = await tableBody.evaluate((node) => ({
|
||||||
|
scrollTop: node.scrollTop,
|
||||||
|
clientHeight: node.clientHeight,
|
||||||
|
scrollHeight: node.scrollHeight
|
||||||
|
}));
|
||||||
|
|
||||||
|
// eslint-disable-next-line playwright/no-conditional-in-test
|
||||||
|
if (top) {
|
||||||
|
return scrollTop;
|
||||||
|
} else {
|
||||||
|
return Math.abs(scrollHeight - (scrollTop + clientHeight));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -0,0 +1,64 @@
|
|||||||
|
/*****************************************************************************
|
||||||
|
* Open MCT, Copyright (c) 2014-2024, United States Government
|
||||||
|
* as represented by the Administrator of the National Aeronautics and Space
|
||||||
|
* Administration. All rights reserved.
|
||||||
|
*
|
||||||
|
* Open MCT is licensed under the Apache License, Version 2.0 (the
|
||||||
|
* "License"); you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0.
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
* License for the specific language governing permissions and limitations
|
||||||
|
* under the License.
|
||||||
|
*
|
||||||
|
* Open MCT includes source code licensed under additional open source
|
||||||
|
* licenses. See the Open Source Licenses file (LICENSES.md) included with
|
||||||
|
* this source code distribution or the Licensing information page available
|
||||||
|
* at runtime from the About dialog for additional information.
|
||||||
|
*****************************************************************************/
|
||||||
|
|
||||||
|
import {
|
||||||
|
createDomainObjectWithDefaults,
|
||||||
|
setIndependentTimeConductorBounds
|
||||||
|
} from '../../../../appActions.js';
|
||||||
|
import { expect, test } from '../../../../pluginFixtures.js';
|
||||||
|
|
||||||
|
const FIXED_TIME =
|
||||||
|
'./#/browse/mine?tc.mode=fixed&tc.startBound=1693592063607&tc.endBound=1693593893607&tc.timeSystem=utc&view=grid&hideInspector=true&hideTree=true';
|
||||||
|
test.describe('Datepicker operations', () => {
|
||||||
|
test.beforeEach(async ({ page }) => {
|
||||||
|
await page.goto(FIXED_TIME);
|
||||||
|
});
|
||||||
|
test('Verify that user can use the datepicker in the TC', async ({ page }) => {
|
||||||
|
await page.getByLabel('Time Conductor Mode').click();
|
||||||
|
// Click on the date picker that is left-most on the screen
|
||||||
|
await page.getByLabel('Global Time Conductor').locator('a').first().click();
|
||||||
|
await expect(page.getByRole('dialog')).toBeVisible();
|
||||||
|
// Click on the first cell
|
||||||
|
await page.getByText('27 239').click();
|
||||||
|
// Expect datepicker to close and time conductor date setting to be changed
|
||||||
|
await expect(page.getByRole('dialog')).toHaveCount(0);
|
||||||
|
});
|
||||||
|
test('Verify that user can use the datepicker in the ITC', async ({ page }) => {
|
||||||
|
const createdTimeList = await createDomainObjectWithDefaults(page, { type: 'Time List' });
|
||||||
|
|
||||||
|
await page.goto(createdTimeList.url, { waitUntil: 'domcontentloaded' });
|
||||||
|
|
||||||
|
await setIndependentTimeConductorBounds(page, {
|
||||||
|
start: '2024-11-12 19:11:11.000Z',
|
||||||
|
end: '2024-11-12 20:11:11.000Z'
|
||||||
|
});
|
||||||
|
// Open ITC
|
||||||
|
await page.getByLabel('Start bounds').nth(0).click();
|
||||||
|
// Click on the datepicker icon
|
||||||
|
await page.locator('form a').first().click();
|
||||||
|
await expect(page.getByRole('dialog')).toBeVisible();
|
||||||
|
// Click on the first cell
|
||||||
|
await page.getByText('7 342').click();
|
||||||
|
// Expect datepicker to close and time conductor date setting to be changed
|
||||||
|
await expect(page.getByRole('dialog')).toHaveCount(0);
|
||||||
|
});
|
||||||
|
});
|
@ -48,7 +48,7 @@ test.describe('Time conductor operations', () => {
|
|||||||
await setTimeConductorBounds(page, startDate);
|
await setTimeConductorBounds(page, startDate);
|
||||||
|
|
||||||
// Bring up the time conductor popup
|
// Bring up the time conductor popup
|
||||||
const timeConductorMode = await page.locator('.c-compact-tc');
|
const timeConductorMode = page.locator('.c-compact-tc');
|
||||||
await timeConductorMode.click();
|
await timeConductorMode.click();
|
||||||
const startDateLocator = page.locator('input[type="text"]').first();
|
const startDateLocator = page.locator('input[type="text"]').first();
|
||||||
const endDateLocator = page.locator('input[type="text"]').nth(2);
|
const endDateLocator = page.locator('input[type="text"]').nth(2);
|
||||||
|
@ -109,7 +109,7 @@ test.describe('Verify tooltips', () => {
|
|||||||
|
|
||||||
async function getToolTip(object) {
|
async function getToolTip(object) {
|
||||||
await page.locator('.c-create-button').hover();
|
await page.locator('.c-create-button').hover();
|
||||||
await page.getByRole('cell', { name: object.name }).hover();
|
await page.getByLabel('lad name').getByText(object.name).hover();
|
||||||
let tooltipText = await page.locator('.c-tooltip').textContent();
|
let tooltipText = await page.locator('.c-tooltip').textContent();
|
||||||
return tooltipText.replace('\n', '').trim();
|
return tooltipText.replace('\n', '').trim();
|
||||||
}
|
}
|
||||||
@ -265,8 +265,8 @@ test.describe('Verify tooltips', () => {
|
|||||||
name: 'Test Flexible Layout'
|
name: 'Test Flexible Layout'
|
||||||
});
|
});
|
||||||
|
|
||||||
await page.dragAndDrop(`text=${sineWaveObject1.name}`, '.c-fl__container >> nth=0');
|
await page.dragAndDrop(`text=${sineWaveObject1.name}`, '.c-fl-container >> nth=0');
|
||||||
await page.dragAndDrop(`text=${sineWaveObject3.name}`, '.c-fl__container >> nth=1');
|
await page.dragAndDrop(`text=${sineWaveObject3.name}`, '.c-fl-container >> nth=1');
|
||||||
|
|
||||||
await page.locator('button[title="Save"]').click();
|
await page.locator('button[title="Save"]').click();
|
||||||
await page.getByRole('listitem', { name: 'Save and Finish Editing' }).click();
|
await page.getByRole('listitem', { name: 'Save and Finish Editing' }).click();
|
||||||
|
75
e2e/tests/functional/ui/inspector.e2e.spec.js
Normal file
@ -0,0 +1,75 @@
|
|||||||
|
/*****************************************************************************
|
||||||
|
* Open MCT, Copyright (c) 2014-2024, United States Government
|
||||||
|
* as represented by the Administrator of the National Aeronautics and Space
|
||||||
|
* Administration. All rights reserved.
|
||||||
|
*
|
||||||
|
* Open MCT is licensed under the Apache License, Version 2.0 (the
|
||||||
|
* "License"); you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0.
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
* License for the specific language governing permissions and limitations
|
||||||
|
* under the License.
|
||||||
|
*
|
||||||
|
* Open MCT includes source code licensed under additional open source
|
||||||
|
* licenses. See the Open Source Licenses file (LICENSES.md) included with
|
||||||
|
* this source code distribution or the Licensing information page available
|
||||||
|
* at runtime from the About dialog for additional information.
|
||||||
|
*****************************************************************************/
|
||||||
|
import { createDomainObjectWithDefaults } from '../../../appActions.js';
|
||||||
|
import { expect, test } from '../../../baseFixtures.js';
|
||||||
|
|
||||||
|
// We don't need cspell to check this. It doesn't know latin.
|
||||||
|
/* cSpell:disable */
|
||||||
|
const loremIpsum = `Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Molestie at elementum eu facilisis sed. Feugiat pretium nibh ipsum consequat. Amet consectetur adipiscing elit duis tristique sollicitudin nibh sit amet. Eget nullam non nisi est sit amet. A pellentesque sit amet porttitor eget dolor morbi non arcu. Ullamcorper sit amet risus nullam eget felis eget nunc. In tellus integer feugiat scelerisque varius morbi enim nunc. Ac feugiat sed lectus vestibulum mattis ullamcorper. Nulla facilisi morbi tempus iaculis urna id volutpat. Massa vitae tortor condimentum lacinia quis vel eros donec. Ornare quam viverra orci sagittis eu. Vestibulum sed arcu non odio. In egestas erat imperdiet sed euismod nisi porta lorem. Vitae auctor eu augue ut lectus arcu bibendum at. Donec adipiscing tristique risus nec feugiat in fermentum posuere urna. Velit euismod in pellentesque massa placerat duis ultricies. Nulla facilisi nullam vehicula ipsum a arcu cursus vitae. Aliquam malesuada bibendum arcu vitae elementum curabitur.
|
||||||
|
Vel eros donec ac odio tempor orci. Et netus et malesuada fames ac turpis egestas sed tempus. Turpis egestas pretium aenean pharetra magna ac placerat. Euismod elementum nisi quis eleifend. Vitae auctor eu augue ut lectus arcu. At imperdiet dui accumsan sit amet nulla facilisi. Est velit egestas dui id ornare arcu odio ut sem. Ornare arcu dui vivamus arcu felis. Luctus venenatis lectus magna fringilla. At elementum eu facilisis sed. Tristique et egestas quis ipsum suspendisse ultrices gravida dictum. Enim eu turpis egestas pretium aenean pharetra magna ac placerat. Lobortis scelerisque fermentum dui faucibus in. Tempor orci eu lobortis elementum nibh tellus molestie nunc non. Dignissim convallis aenean et tortor at risus. Enim tortor at auctor urna nunc id cursus. Libero volutpat sed cras ornare arcu dui vivamus. Scelerisque fermentum dui faucibus in ornare quam viverra.
|
||||||
|
Odio ut sem nulla pharetra. Neque vitae tempus quam pellentesque nec. A arcu cursus vitae congue mauris. Turpis nunc eget lorem dolor sed viverra ipsum nunc aliquet. Nibh tellus molestie nunc non blandit massa enim nec. Risus feugiat in ante metus dictum at tempor commodo ullamcorper. Nec tincidunt praesent semper feugiat nibh sed pulvinar proin gravida. Pulvinar elementum integer enim neque. Bibendum ut tristique et egestas. Nibh praesent tristique magna sit. Lectus magna fringilla urna porttitor. Eu non diam phasellus vestibulum lorem sed risus. Rhoncus mattis rhoncus urna neque. Rutrum tellus pellentesque eu tincidunt tortor aliquam. Pharetra convallis posuere morbi leo urna molestie at elementum. Quis commodo odio aenean sed adipiscing. Enim sit amet venenatis urna cursus eget nunc.
|
||||||
|
Enim nec dui nunc mattis. Cursus turpis massa tincidunt dui ut. Donec adipiscing tristique risus nec feugiat in. Eleifend mi in nulla posuere sollicitudin. Donec enim diam vulputate ut pharetra sit. Ultricies mi eget mauris pharetra et ultrices neque. Eros in cursus turpis massa tincidunt dui. Cursus risus at ultrices mi tempus imperdiet nulla malesuada. Morbi enim nunc faucibus a pellentesque sit. Porttitor rhoncus dolor purus non. Ac tortor vitae purus faucibus.
|
||||||
|
Proin libero nunc consequat interdum varius sit amet mattis vulputate. Metus dictum at tempor commodo ullamcorper a lacus vestibulum sed. Quisque non tellus orci ac auctor augue mauris. Id ornare arcu odio ut. Rhoncus est pellentesque elit ullamcorper dignissim. Senectus et netus et malesuada fames ac turpis egestas. Volutpat ac tincidunt vitae semper quis lectus nulla. Adipiscing elit duis tristique sollicitudin. Ipsum faucibus vitae aliquet nec ullamcorper sit. Gravida neque convallis a cras semper auctor neque vitae tempus. Porttitor leo a diam sollicitudin tempor id. Dictum non consectetur a erat nam at lectus. At volutpat diam ut venenatis tellus in. Morbi enim nunc faucibus a pellentesque sit amet. Cursus in hac habitasse platea. Sed augue lacus viverra vitae.
|
||||||
|
`;
|
||||||
|
|
||||||
|
test.describe('Inspector tests', () => {
|
||||||
|
test.beforeEach(async ({ page }) => {
|
||||||
|
await page.goto('./', { waitUntil: 'domcontentloaded' });
|
||||||
|
});
|
||||||
|
|
||||||
|
test('Content in inspector can be scrolled to vertically', async ({ page }) => {
|
||||||
|
const folderWithOverflowingTitle = await createDomainObjectWithDefaults(page, {
|
||||||
|
type: 'Folder',
|
||||||
|
name: loremIpsum
|
||||||
|
});
|
||||||
|
|
||||||
|
await page.goto(folderWithOverflowingTitle.url);
|
||||||
|
|
||||||
|
const inspectorPropertiesLocator = page
|
||||||
|
.getByRole('tabpanel', { name: 'Inspector Views' })
|
||||||
|
.getByLabel('Inspector Properties Details');
|
||||||
|
const inspectorPropertiesList = inspectorPropertiesLocator.getByRole('list');
|
||||||
|
const firstInspectorPropertyValue = inspectorPropertiesList
|
||||||
|
.getByRole('listitem')
|
||||||
|
.first()
|
||||||
|
.getByLabel('value', { exact: false });
|
||||||
|
const lastInspectorPropertyValue = inspectorPropertiesList
|
||||||
|
.getByRole('listitem')
|
||||||
|
.last()
|
||||||
|
.getByLabel('value', { exact: false });
|
||||||
|
|
||||||
|
// inspector content partially in viewport, but not all the way in viewport
|
||||||
|
await expect(inspectorPropertiesLocator).toBeInViewport();
|
||||||
|
await expect(inspectorPropertiesLocator).not.toBeInViewport({ ratio: 0.9 });
|
||||||
|
|
||||||
|
await expect(firstInspectorPropertyValue).toBeInViewport();
|
||||||
|
await expect(lastInspectorPropertyValue).not.toBeInViewport();
|
||||||
|
|
||||||
|
// using page.mouse.wheel to scroll the inspector content by the height of the content
|
||||||
|
// because click and scrollIntoView will scroll even if scrollbar not available
|
||||||
|
await inspectorPropertiesLocator.hover();
|
||||||
|
const offset = await inspectorPropertiesLocator.evaluate((el) => el.offsetHeight);
|
||||||
|
await page.mouse.wheel(0, offset);
|
||||||
|
|
||||||
|
await expect(lastInspectorPropertyValue).toBeInViewport();
|
||||||
|
});
|
||||||
|
});
|
@ -34,7 +34,7 @@ TODO:
|
|||||||
|
|
||||||
import { expect, test } from '@playwright/test';
|
import { expect, test } from '@playwright/test';
|
||||||
|
|
||||||
const filePath = 'e2e/test-data/PerformanceDisplayLayout.json';
|
const filePath = 'test-data/PerformanceDisplayLayout.json';
|
||||||
|
|
||||||
test.describe('Performance tests', () => {
|
test.describe('Performance tests', () => {
|
||||||
test.beforeEach(async ({ page, browser }, testInfo) => {
|
test.beforeEach(async ({ page, browser }, testInfo) => {
|
||||||
@ -178,7 +178,7 @@ test.describe('Performance tests', () => {
|
|||||||
console.log('jpgResourceTiming ' + JSON.stringify(jpgResourceTiming));
|
console.log('jpgResourceTiming ' + JSON.stringify(jpgResourceTiming));
|
||||||
|
|
||||||
// Click Close Icon
|
// Click Close Icon
|
||||||
await page.locator('[aria-label="Close"]').click();
|
await page.getByRole('button', { name: 'Close' }).click();
|
||||||
await page.evaluate(() => window.performance.mark('view-large-close-button'));
|
await page.evaluate(() => window.performance.mark('view-large-close-button'));
|
||||||
|
|
||||||
//await client.send('HeapProfiler.enable');
|
//await client.send('HeapProfiler.enable');
|
||||||
|
@ -33,7 +33,7 @@ TODO:
|
|||||||
|
|
||||||
import { expect, test } from '@playwright/test';
|
import { expect, test } from '@playwright/test';
|
||||||
|
|
||||||
const notebookFilePath = 'e2e/test-data/PerformanceNotebook.json';
|
const notebookFilePath = 'test-data/PerformanceNotebook.json';
|
||||||
|
|
||||||
test.describe('Performance tests', () => {
|
test.describe('Performance tests', () => {
|
||||||
test.beforeEach(async ({ page, browser }, testInfo) => {
|
test.beforeEach(async ({ page, browser }, testInfo) => {
|
||||||
|
@ -299,7 +299,6 @@ test.describe('Navigation memory leak is not detected in', () => {
|
|||||||
// for detecting memory leaks.
|
// for detecting memory leaks.
|
||||||
await page.evaluate(() => {
|
await page.evaluate(() => {
|
||||||
window.gcPromise = new Promise((resolve) => {
|
window.gcPromise = new Promise((resolve) => {
|
||||||
// eslint-disable-next-line no-undef
|
|
||||||
window.fr = new FinalizationRegistry(resolve);
|
window.fr = new FinalizationRegistry(resolve);
|
||||||
window.fr.register(
|
window.fr.register(
|
||||||
window.openmct.layout.$refs.browseObject.$refs.objectViewWrapper.firstChild,
|
window.openmct.layout.$refs.browseObject.$refs.objectViewWrapper.firstChild,
|
||||||
|
@ -21,14 +21,13 @@
|
|||||||
*****************************************************************************/
|
*****************************************************************************/
|
||||||
|
|
||||||
import { test } from '../../avpFixtures.js';
|
import { test } from '../../avpFixtures.js';
|
||||||
import { VISUAL_URL } from '../../constants.js';
|
import { VISUAL_FIXED_URL } from '../../constants.js';
|
||||||
|
|
||||||
test.describe('a11y - Default', () => {
|
test.describe('a11y - Default', () => {
|
||||||
test.beforeEach(async ({ page }) => {
|
test.beforeEach(async ({ page }) => {
|
||||||
await page.goto(VISUAL_URL, { waitUntil: 'domcontentloaded' });
|
await page.goto(VISUAL_FIXED_URL, { waitUntil: 'domcontentloaded' });
|
||||||
});
|
});
|
||||||
test('main view', async ({ page }, testInfo) => {
|
test('main view', async ({ page }, testInfo) => {
|
||||||
await page.goto('./');
|
|
||||||
//Skipping for https://github.com/nasa/openmct/issues/7421
|
//Skipping for https://github.com/nasa/openmct/issues/7421
|
||||||
//await scanForA11yViolations(page, testInfo.title);
|
//await scanForA11yViolations(page, testInfo.title);
|
||||||
});
|
});
|
||||||
|
@ -27,12 +27,12 @@ Tests the branding associated with the default deployment. At least the about mo
|
|||||||
import percySnapshot from '@percy/playwright';
|
import percySnapshot from '@percy/playwright';
|
||||||
|
|
||||||
import { expect, scanForA11yViolations, test } from '../../../avpFixtures.js';
|
import { expect, scanForA11yViolations, test } from '../../../avpFixtures.js';
|
||||||
import { VISUAL_URL } from '../../../constants.js';
|
import { VISUAL_FIXED_URL } from '../../../constants.js';
|
||||||
|
|
||||||
test.describe('Visual - Branding @a11y', () => {
|
test.describe('Visual - Branding @a11y', () => {
|
||||||
test.beforeEach(async ({ page }) => {
|
test.beforeEach(async ({ page }) => {
|
||||||
//Go to baseURL and Hide Tree
|
//Go to baseURL and Hide Tree
|
||||||
await page.goto(VISUAL_URL, { waitUntil: 'domcontentloaded' });
|
await page.goto(VISUAL_FIXED_URL, { waitUntil: 'domcontentloaded' });
|
||||||
});
|
});
|
||||||
|
|
||||||
test('Visual - About Modal', async ({ page, theme }) => {
|
test('Visual - About Modal', async ({ page, theme }) => {
|
||||||
|
@ -28,7 +28,7 @@ import percySnapshot from '@percy/playwright';
|
|||||||
import { fileURLToPath } from 'url';
|
import { fileURLToPath } from 'url';
|
||||||
|
|
||||||
import { expect, test } from '../../../avpFixtures.js';
|
import { expect, test } from '../../../avpFixtures.js';
|
||||||
import { VISUAL_URL } from '../../../constants.js';
|
import { VISUAL_FIXED_URL } from '../../../constants.js';
|
||||||
|
|
||||||
//Declare the component scope of the visual test for Percy
|
//Declare the component scope of the visual test for Percy
|
||||||
const header = '.l-shell__head';
|
const header = '.l-shell__head';
|
||||||
@ -36,7 +36,7 @@ const header = '.l-shell__head';
|
|||||||
test.describe('Visual - Header @a11y', () => {
|
test.describe('Visual - Header @a11y', () => {
|
||||||
test.beforeEach(async ({ page }) => {
|
test.beforeEach(async ({ page }) => {
|
||||||
//Go to baseURL and Hide Tree
|
//Go to baseURL and Hide Tree
|
||||||
await page.goto(VISUAL_URL, { waitUntil: 'domcontentloaded' });
|
await page.goto(VISUAL_FIXED_URL, { waitUntil: 'domcontentloaded' });
|
||||||
// Wait for status bar to load
|
// Wait for status bar to load
|
||||||
await expect(
|
await expect(
|
||||||
page.getByRole('status', {
|
page.getByRole('status', {
|
||||||
@ -69,14 +69,14 @@ test.describe('Visual - Header @a11y', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
test('show snapshot button', async ({ page, theme }) => {
|
test('show snapshot button', async ({ page, theme }) => {
|
||||||
await page.getByLabel('Take a Notebook Snapshot').click();
|
await page.getByLabel('Open the Notebook Snapshot Menu').click();
|
||||||
|
|
||||||
await page.getByRole('menuitem', { name: 'Save to Notebook Snapshots' }).click();
|
await page.getByRole('menuitem', { name: 'Save to Notebook Snapshots' }).click();
|
||||||
|
|
||||||
await percySnapshot(page, `Notebook Snapshot Show button (theme: '${theme}')`, {
|
await percySnapshot(page, `Notebook Snapshot Show button (theme: '${theme}')`, {
|
||||||
scope: header
|
scope: header
|
||||||
});
|
});
|
||||||
await expect(await page.getByLabel('Show Snapshots')).toBeVisible();
|
await expect(page.getByLabel('Show Snapshots')).toBeVisible();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -23,17 +23,17 @@
|
|||||||
import percySnapshot from '@percy/playwright';
|
import percySnapshot from '@percy/playwright';
|
||||||
|
|
||||||
import { test } from '../../../avpFixtures.js';
|
import { test } from '../../../avpFixtures.js';
|
||||||
import { MISSION_TIME, VISUAL_URL } from '../../../constants.js';
|
import { MISSION_TIME, VISUAL_FIXED_URL } from '../../../constants.js';
|
||||||
|
|
||||||
//Declare the scope of the visual test
|
//Declare the scope of the visual test
|
||||||
const inspectorPane = '.l-shell__pane-inspector';
|
const inspectorPane = '.l-shell__pane-inspector';
|
||||||
|
|
||||||
test.describe('Visual - Inspector @ally @clock', () => {
|
test.describe('Visual - Inspector @ally @clock', () => {
|
||||||
test.beforeEach(async ({ page }) => {
|
test.beforeEach(async ({ page }) => {
|
||||||
await page.goto(VISUAL_URL, { waitUntil: 'domcontentloaded' });
|
await page.goto(VISUAL_FIXED_URL, { waitUntil: 'domcontentloaded' });
|
||||||
});
|
});
|
||||||
test.use({
|
test.use({
|
||||||
storageState: './e2e/test-data/overlay_plot_with_delay_storage.json',
|
storageState: 'test-data/overlay_plot_with_delay_storage.json',
|
||||||
clockOptions: {
|
clockOptions: {
|
||||||
now: MISSION_TIME,
|
now: MISSION_TIME,
|
||||||
shouldAdvanceTime: true
|
shouldAdvanceTime: true
|
||||||
|
116
e2e/tests/visual-a11y/components/timeConductor.visual.spec.js
Normal file
@ -0,0 +1,116 @@
|
|||||||
|
/*****************************************************************************
|
||||||
|
* Open MCT, Copyright (c) 2014-2024, United States Government
|
||||||
|
* as represented by the Administrator of the National Aeronautics and Space
|
||||||
|
* Administration. All rights reserved.
|
||||||
|
*
|
||||||
|
* Open MCT is licensed under the Apache License, Version 2.0 (the
|
||||||
|
* "License"); you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0.
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
* License for the specific language governing permissions and limitations
|
||||||
|
* under the License.
|
||||||
|
*
|
||||||
|
* Open MCT includes source code licensed under additional open source
|
||||||
|
* licenses. See the Open Source Licenses file (LICENSES.md) included with
|
||||||
|
* this source code distribution or the Licensing information page available
|
||||||
|
* at runtime from the About dialog for additional information.
|
||||||
|
*****************************************************************************/
|
||||||
|
|
||||||
|
/*
|
||||||
|
Tests the visual appearance of the Time Conductor component
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { expect, test } from '../../../avpFixtures.js';
|
||||||
|
import {
|
||||||
|
MISSION_TIME,
|
||||||
|
MISSION_TIME_FIXED_END,
|
||||||
|
MISSION_TIME_FIXED_START,
|
||||||
|
VISUAL_REALTIME_URL
|
||||||
|
} from '../../../constants.js';
|
||||||
|
|
||||||
|
test.describe('Visual - Time Conductor', () => {
|
||||||
|
test.use({
|
||||||
|
clockOptions: {
|
||||||
|
now: MISSION_TIME,
|
||||||
|
shouldAdvanceTime: false
|
||||||
|
}
|
||||||
|
});
|
||||||
|
test.beforeEach(async ({ page }) => {
|
||||||
|
await page.goto('./', { waitUntil: 'domcontentloaded' });
|
||||||
|
});
|
||||||
|
|
||||||
|
// FIXME: checking for a11y violations times out. Might have something to do with the frozen clock.
|
||||||
|
// test.afterEach(async ({ page }, testInfo) => {
|
||||||
|
// await scanForA11yViolations(page, testInfo.title);
|
||||||
|
// });
|
||||||
|
|
||||||
|
test('Visual - Time Conductor (Fixed time) @clock @snapshot', async ({ page }) => {
|
||||||
|
// Navigate to a specific view that uses the Time Conductor in Fixed Time mode with inspect and browse panes collapsed
|
||||||
|
await page.goto(
|
||||||
|
`./#/browse/mine?tc.mode=fixed&tc.startBound=${MISSION_TIME_FIXED_START}&tc.endBound=${MISSION_TIME_FIXED_END}&tc.timeSystem=utc&view=grid&hideInspector=true&hideTree=true`,
|
||||||
|
{
|
||||||
|
waitUntil: 'domcontentloaded'
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
// Take a snapshot for comparison
|
||||||
|
const snapshot = await page.screenshot({
|
||||||
|
mask: []
|
||||||
|
});
|
||||||
|
expect(snapshot).toMatchSnapshot('time-conductor-fixed-time.png');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('Visual - Time Conductor (Realtime) @clock @snapshot', async ({ page }) => {
|
||||||
|
// Navigate to a specific view that uses the Time Conductor in Fixed Time mode with inspect and browse panes collapsed
|
||||||
|
await page.goto(VISUAL_REALTIME_URL, {
|
||||||
|
waitUntil: 'domcontentloaded'
|
||||||
|
});
|
||||||
|
|
||||||
|
const mask = [];
|
||||||
|
|
||||||
|
// Take a snapshot for comparison
|
||||||
|
const snapshot = await page.screenshot({
|
||||||
|
mask
|
||||||
|
});
|
||||||
|
expect(snapshot).toMatchSnapshot('time-conductor-realtime.png');
|
||||||
|
});
|
||||||
|
test(
|
||||||
|
'Visual - Time Conductor Axis Resized @clock @snapshot',
|
||||||
|
{ annotation: [{ type: 'issue', description: 'https://github.com/nasa/openmct/issues/7623' }] },
|
||||||
|
async ({ page, tick }) => {
|
||||||
|
const VISUAL_REALTIME_WITH_PANES = VISUAL_REALTIME_URL.replace(
|
||||||
|
'hideTree=true',
|
||||||
|
'hideTree=false'
|
||||||
|
).replace('hideInspector=true', 'hideInspector=false');
|
||||||
|
// Navigate to a specific view that uses the Time Conductor in Fixed Time mode with inspect
|
||||||
|
await page.goto(VISUAL_REALTIME_WITH_PANES, {
|
||||||
|
waitUntil: 'domcontentloaded'
|
||||||
|
});
|
||||||
|
|
||||||
|
// Set the time conductor to fixed time mode
|
||||||
|
await page.getByLabel('Time Conductor Mode').click();
|
||||||
|
await page.getByLabel('Time Conductor Mode Menu').click();
|
||||||
|
await page.getByLabel('Fixed Timespan').click();
|
||||||
|
await page.getByLabel('Submit time bounds').click();
|
||||||
|
|
||||||
|
// Collapse the inspect and browse panes to trigger a resize of the conductor axis
|
||||||
|
await page.getByLabel('Collapse Inspect Pane').click();
|
||||||
|
await page.getByLabel('Collapse Browse Pane').click();
|
||||||
|
|
||||||
|
// manually tick the clock to trigger the resize / re-render
|
||||||
|
await tick(1000 * 2);
|
||||||
|
|
||||||
|
const mask = [];
|
||||||
|
|
||||||
|
// Take a snapshot for comparison
|
||||||
|
const snapshot = await page.screenshot({
|
||||||
|
mask
|
||||||
|
});
|
||||||
|
expect(snapshot).toMatchSnapshot('time-conductor-axis-resized.png');
|
||||||
|
}
|
||||||
|
);
|
||||||
|
});
|
After Width: | Height: | Size: 33 KiB |
After Width: | Height: | Size: 34 KiB |
After Width: | Height: | Size: 29 KiB |
@ -23,7 +23,7 @@
|
|||||||
import percySnapshot from '@percy/playwright';
|
import percySnapshot from '@percy/playwright';
|
||||||
|
|
||||||
import { createDomainObjectWithDefaults, expandTreePaneItemByName } from '../../../appActions.js';
|
import { createDomainObjectWithDefaults, expandTreePaneItemByName } from '../../../appActions.js';
|
||||||
import { VISUAL_URL } from '../../../constants.js';
|
import { VISUAL_FIXED_URL } from '../../../constants.js';
|
||||||
import { test } from '../../../pluginFixtures.js';
|
import { test } from '../../../pluginFixtures.js';
|
||||||
|
|
||||||
//Declare the scope of the visual test
|
//Declare the scope of the visual test
|
||||||
@ -32,7 +32,7 @@ const treePane = "[role=tree][aria-label='Main Tree']";
|
|||||||
test.describe('Visual - Tree Pane', () => {
|
test.describe('Visual - Tree Pane', () => {
|
||||||
test('Tree pane in various states', async ({ page, theme, openmctConfig }) => {
|
test('Tree pane in various states', async ({ page, theme, openmctConfig }) => {
|
||||||
const { myItemsFolderName } = openmctConfig;
|
const { myItemsFolderName } = openmctConfig;
|
||||||
await page.goto(VISUAL_URL, { waitUntil: 'domcontentloaded' });
|
await page.goto(VISUAL_FIXED_URL, { waitUntil: 'domcontentloaded' });
|
||||||
|
|
||||||
//Open Tree
|
//Open Tree
|
||||||
await page.getByRole('button', { name: 'Browse' }).click();
|
await page.getByRole('button', { name: 'Browse' }).click();
|
||||||
|
@ -27,15 +27,15 @@ clockOptions plugin fixture.
|
|||||||
|
|
||||||
import percySnapshot from '@percy/playwright';
|
import percySnapshot from '@percy/playwright';
|
||||||
|
|
||||||
import { MISSION_TIME, VISUAL_URL } from '../../constants.js';
|
import { MISSION_TIME, VISUAL_FIXED_URL } from '../../constants.js';
|
||||||
import { expect, test } from '../../pluginFixtures.js';
|
import { expect, test } from '../../pluginFixtures.js';
|
||||||
|
|
||||||
test.describe('Visual - Controlled Clock @clock', () => {
|
test.describe('Visual - Controlled Clock @clock', () => {
|
||||||
test.beforeEach(async ({ page }) => {
|
test.beforeEach(async ({ page }) => {
|
||||||
await page.goto(VISUAL_URL, { waitUntil: 'domcontentloaded' });
|
await page.goto(VISUAL_FIXED_URL, { waitUntil: 'domcontentloaded' });
|
||||||
});
|
});
|
||||||
test.use({
|
test.use({
|
||||||
storageState: './e2e/test-data/overlay_plot_with_delay_storage.json',
|
storageState: 'test-data/overlay_plot_with_delay_storage.json',
|
||||||
clockOptions: {
|
clockOptions: {
|
||||||
now: MISSION_TIME,
|
now: MISSION_TIME,
|
||||||
shouldAdvanceTime: false //Don't advance the clock
|
shouldAdvanceTime: false //Don't advance the clock
|
||||||
@ -43,7 +43,7 @@ test.describe('Visual - Controlled Clock @clock', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
test('Overlay Plot Loading Indicator @localStorage', async ({ page, theme }) => {
|
test('Overlay Plot Loading Indicator @localStorage', async ({ page, theme }) => {
|
||||||
await page.goto(VISUAL_URL, { waitUntil: 'domcontentloaded' });
|
await page.goto(VISUAL_FIXED_URL, { waitUntil: 'domcontentloaded' });
|
||||||
await page
|
await page
|
||||||
.getByRole('gridcell', { hasText: 'Overlay Plot with 5s Delay Overlay Plot' })
|
.getByRole('gridcell', { hasText: 'Overlay Plot with 5s Delay Overlay Plot' })
|
||||||
.click();
|
.click();
|
||||||
|
@ -23,18 +23,18 @@
|
|||||||
/*
|
/*
|
||||||
Collection of Visual Tests set to run in a default context with default Plugins. The tests within this suite
|
Collection of Visual Tests set to run in a default context with default Plugins. The tests within this suite
|
||||||
are only meant to run against openmct's app.js started by `npm run start` within the
|
are only meant to run against openmct's app.js started by `npm run start` within the
|
||||||
`./e2e/playwright-visual.config.js` file.
|
`playwright-visual.config.js` file.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import percySnapshot from '@percy/playwright';
|
import percySnapshot from '@percy/playwright';
|
||||||
|
|
||||||
import { createDomainObjectWithDefaults } from '../../appActions.js';
|
import { createDomainObjectWithDefaults } from '../../appActions.js';
|
||||||
import { expect, scanForA11yViolations, test } from '../../avpFixtures.js';
|
import { expect, scanForA11yViolations, test } from '../../avpFixtures.js';
|
||||||
import { VISUAL_URL } from '../../constants.js';
|
import { VISUAL_FIXED_URL } from '../../constants.js';
|
||||||
|
|
||||||
test.describe('Visual - Default @a11y', () => {
|
test.describe('Visual - Default @a11y', () => {
|
||||||
test.beforeEach(async ({ page }) => {
|
test.beforeEach(async ({ page }) => {
|
||||||
await page.goto(VISUAL_URL, { waitUntil: 'domcontentloaded' });
|
await page.goto(VISUAL_FIXED_URL, { waitUntil: 'domcontentloaded' });
|
||||||
});
|
});
|
||||||
|
|
||||||
test('Visual - Default Dashboard', async ({ page, theme }) => {
|
test('Visual - Default Dashboard', async ({ page, theme }) => {
|
||||||
|
@ -23,12 +23,18 @@
|
|||||||
import percySnapshot from '@percy/playwright';
|
import percySnapshot from '@percy/playwright';
|
||||||
|
|
||||||
import { createDomainObjectWithDefaults } from '../../appActions.js';
|
import { createDomainObjectWithDefaults } from '../../appActions.js';
|
||||||
import { VISUAL_URL } from '../../constants.js';
|
import { MISSION_TIME, VISUAL_FIXED_URL } from '../../constants.js';
|
||||||
import { test } from '../../pluginFixtures.js';
|
import { test } from '../../pluginFixtures.js';
|
||||||
|
|
||||||
test.describe('Visual - Display Layout', () => {
|
test.describe('Visual - Display Layout @clock', () => {
|
||||||
test.beforeEach(async ({ page, theme }) => {
|
test.use({
|
||||||
await page.goto(VISUAL_URL, { waitUntil: 'domcontentloaded' });
|
clockOptions: {
|
||||||
|
now: MISSION_TIME,
|
||||||
|
shouldAdvanceTime: true
|
||||||
|
}
|
||||||
|
});
|
||||||
|
test.beforeEach(async ({ page }) => {
|
||||||
|
await page.goto(VISUAL_FIXED_URL, { waitUntil: 'domcontentloaded' });
|
||||||
|
|
||||||
const parentLayout = await createDomainObjectWithDefaults(page, {
|
const parentLayout = await createDomainObjectWithDefaults(page, {
|
||||||
type: 'Display Layout',
|
type: 'Display Layout',
|
||||||
@ -59,12 +65,15 @@ test.describe('Visual - Display Layout', () => {
|
|||||||
await page.goto(parentLayout.url, { waitUntil: 'domcontentloaded' });
|
await page.goto(parentLayout.url, { waitUntil: 'domcontentloaded' });
|
||||||
await page.getByRole('button', { name: 'Edit Object' }).click();
|
await page.getByRole('button', { name: 'Edit Object' }).click();
|
||||||
|
|
||||||
//Move the Child Right Layout to the Right. It should be on top of the Left Layout at this point.
|
// Select the child right layout
|
||||||
await page
|
await page
|
||||||
.getByLabel('Child Right Layout Layout', { exact: true })
|
.getByLabel('Child Right Layout Layout', { exact: true })
|
||||||
.getByLabel('Move Sub-object Frame')
|
.getByLabel('Move Sub-object Frame')
|
||||||
.click();
|
.click();
|
||||||
await page.getByLabel('Move Sub-object Frame').nth(3).click(); //I'm not sure why this step is necessary
|
// FIXME: Click to select the parent object (layout)
|
||||||
|
await page.getByLabel('Move Sub-object Frame').nth(3).click();
|
||||||
|
|
||||||
|
// Move the second layout element to the right
|
||||||
await page.getByLabel('X:').click();
|
await page.getByLabel('X:').click();
|
||||||
await page.getByLabel('X:').fill('35');
|
await page.getByLabel('X:').fill('35');
|
||||||
});
|
});
|
||||||
|
@ -84,6 +84,12 @@ test.describe('Fault Management Visual Tests', () => {
|
|||||||
await shelveFault(page, 1);
|
await shelveFault(page, 1);
|
||||||
await changeViewTo(page, 'shelved');
|
await changeViewTo(page, 'shelved');
|
||||||
|
|
||||||
|
/* cspell:disable-next-line */
|
||||||
|
// Since fault management is heavily dependent on events (bleh), we need to wait for the correct
|
||||||
|
// element counts
|
||||||
|
await expect(page.getByLabel('Select fault:')).toHaveCount(1);
|
||||||
|
await expect(page.getByLabel('Disposition Actions')).toHaveCount(1);
|
||||||
|
|
||||||
await percySnapshot(page, `Shelved faults appear in the shelved view (theme: '${theme}')`);
|
await percySnapshot(page, `Shelved faults appear in the shelved view (theme: '${theme}')`);
|
||||||
|
|
||||||
await openFaultRowMenu(page, 1);
|
await openFaultRowMenu(page, 1);
|
||||||
|
@ -23,7 +23,8 @@
|
|||||||
import percySnapshot from '@percy/playwright';
|
import percySnapshot from '@percy/playwright';
|
||||||
|
|
||||||
import { createDomainObjectWithDefaults, setRealTimeMode } from '../../appActions.js';
|
import { createDomainObjectWithDefaults, setRealTimeMode } from '../../appActions.js';
|
||||||
import { VISUAL_URL } from '../../constants.js';
|
import { waitForAnimations } from '../../baseFixtures.js';
|
||||||
|
import { VISUAL_FIXED_URL } from '../../constants.js';
|
||||||
import { expect, test } from '../../pluginFixtures.js';
|
import { expect, test } from '../../pluginFixtures.js';
|
||||||
|
|
||||||
test.describe('Visual - Example Imagery', () => {
|
test.describe('Visual - Example Imagery', () => {
|
||||||
@ -31,7 +32,7 @@ test.describe('Visual - Example Imagery', () => {
|
|||||||
let parentLayout;
|
let parentLayout;
|
||||||
|
|
||||||
test.beforeEach(async ({ page }) => {
|
test.beforeEach(async ({ page }) => {
|
||||||
await page.goto(VISUAL_URL, { waitUntil: 'domcontentloaded' });
|
await page.goto(VISUAL_FIXED_URL, { waitUntil: 'domcontentloaded' });
|
||||||
|
|
||||||
parentLayout = await createDomainObjectWithDefaults(page, {
|
parentLayout = await createDomainObjectWithDefaults(page, {
|
||||||
type: 'Display Layout',
|
type: 'Display Layout',
|
||||||
@ -75,11 +76,10 @@ test.describe('Visual - Example Imagery', () => {
|
|||||||
await page.goto(exampleImagery.url, { waitUntil: 'domcontentloaded' });
|
await page.goto(exampleImagery.url, { waitUntil: 'domcontentloaded' });
|
||||||
|
|
||||||
await setRealTimeMode(page, true);
|
await setRealTimeMode(page, true);
|
||||||
//Temporary to close the dialog
|
|
||||||
await page.getByLabel('Submit time offsets').click();
|
|
||||||
|
|
||||||
await expect(page.getByLabel('Image Wrapper')).toBeVisible();
|
await expect(page.getByLabel('Image Wrapper')).toBeVisible();
|
||||||
|
|
||||||
|
await waitForAnimations(page.locator('.animate-scroll'));
|
||||||
await percySnapshot(page, `Example Imagery in Real Time (theme: ${theme})`);
|
await percySnapshot(page, `Example Imagery in Real Time (theme: ${theme})`);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -23,14 +23,14 @@
|
|||||||
import percySnapshot from '@percy/playwright';
|
import percySnapshot from '@percy/playwright';
|
||||||
|
|
||||||
import { createDomainObjectWithDefaults } from '../../appActions.js';
|
import { createDomainObjectWithDefaults } from '../../appActions.js';
|
||||||
import { VISUAL_URL } from '../../constants.js';
|
import { VISUAL_FIXED_URL } from '../../constants.js';
|
||||||
import { expect, test } from '../../pluginFixtures.js';
|
import { expect, test } from '../../pluginFixtures.js';
|
||||||
|
|
||||||
test.describe('Visual - LAD Table', () => {
|
test.describe('Visual - LAD Table', () => {
|
||||||
let ladTable;
|
let ladTable;
|
||||||
|
|
||||||
test.beforeEach(async ({ page }) => {
|
test.beforeEach(async ({ page }) => {
|
||||||
await page.goto(VISUAL_URL, { waitUntil: 'domcontentloaded' });
|
await page.goto(VISUAL_FIXED_URL, { waitUntil: 'domcontentloaded' });
|
||||||
|
|
||||||
// Create LAD Table
|
// Create LAD Table
|
||||||
ladTable = await createDomainObjectWithDefaults(page, {
|
ladTable = await createDomainObjectWithDefaults(page, {
|
||||||
|
@ -23,8 +23,8 @@
|
|||||||
import percySnapshot from '@percy/playwright';
|
import percySnapshot from '@percy/playwright';
|
||||||
|
|
||||||
import { createDomainObjectWithDefaults, expandTreePaneItemByName } from '../../appActions.js';
|
import { createDomainObjectWithDefaults, expandTreePaneItemByName } from '../../appActions.js';
|
||||||
import { test } from '../../avpFixtures.js';
|
import { expect, test } from '../../avpFixtures.js';
|
||||||
import { VISUAL_URL } from '../../constants.js';
|
import { VISUAL_FIXED_URL } from '../../constants.js';
|
||||||
import { enterTextEntry, startAndAddRestrictedNotebookObject } from '../../helper/notebookUtils.js';
|
import { enterTextEntry, startAndAddRestrictedNotebookObject } from '../../helper/notebookUtils.js';
|
||||||
|
|
||||||
test.describe('Visual - Restricted Notebook @a11y', () => {
|
test.describe('Visual - Restricted Notebook @a11y', () => {
|
||||||
@ -39,10 +39,48 @@ test.describe('Visual - Restricted Notebook @a11y', () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test.describe('Visual - Notebook Snapshot @a11y', () => {
|
||||||
|
test.beforeEach(async ({ page }) => {
|
||||||
|
await page.goto('./?hideTree=true&hideInspector=true', { waitUntil: 'domcontentloaded' });
|
||||||
|
});
|
||||||
|
test('Visual check for Snapshot Annotation', async ({ page, theme }) => {
|
||||||
|
await page.getByLabel('Open the Notebook Snapshot Menu').click();
|
||||||
|
await page.getByRole('menuitem', { name: 'Save to Notebook Snapshots' }).click();
|
||||||
|
await page.getByLabel('Show Snapshots').click();
|
||||||
|
|
||||||
|
await page.getByLabel('My Items Notebook Embed').getByLabel('More actions').click();
|
||||||
|
await page.getByRole('menuitem', { name: 'View Snapshot' }).click();
|
||||||
|
|
||||||
|
await page.getByLabel('Annotate this snapshot').click();
|
||||||
|
await expect(page.locator('#snap-annotation-canvas')).toBeVisible();
|
||||||
|
// Clear the canvas
|
||||||
|
await page.getByRole('button', { name: 'Put text [T]' }).click();
|
||||||
|
// Click in the Painterro canvas to add a text annotation
|
||||||
|
await page.locator('.ptro-crp-el').click();
|
||||||
|
await page.locator('.ptro-text-tool-input').fill('...is there life on mars?');
|
||||||
|
await percySnapshot(page, `Notebook Snapshot with text entry open (theme: '${theme}')`);
|
||||||
|
|
||||||
|
// When working with Painterro, we need to check that the Apply button is hidden after clicking
|
||||||
|
await page.getByTitle('Apply').click();
|
||||||
|
await expect(page.getByTitle('Apply')).toBeHidden();
|
||||||
|
|
||||||
|
// Save and exit annotation window
|
||||||
|
await page.getByRole('button', { name: 'Save' }).click();
|
||||||
|
await page.getByRole('button', { name: 'Done' }).click();
|
||||||
|
|
||||||
|
// Open up annotation again
|
||||||
|
await page.getByRole('img', { name: 'My Items thumbnail' }).click();
|
||||||
|
await expect(page.getByLabel('Modal Overlay').getByRole('img')).toBeVisible();
|
||||||
|
|
||||||
|
// Take a snapshot
|
||||||
|
await percySnapshot(page, `Notebook Snapshot with annotation (theme: '${theme}')`);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
test.describe('Visual - Notebook @a11y', () => {
|
test.describe('Visual - Notebook @a11y', () => {
|
||||||
let notebook;
|
let notebook;
|
||||||
test.beforeEach(async ({ page }) => {
|
test.beforeEach(async ({ page }) => {
|
||||||
await page.goto(VISUAL_URL, { waitUntil: 'domcontentloaded' });
|
await page.goto(VISUAL_FIXED_URL, { waitUntil: 'domcontentloaded' });
|
||||||
notebook = await createDomainObjectWithDefaults(page, {
|
notebook = await createDomainObjectWithDefaults(page, {
|
||||||
type: 'Notebook',
|
type: 'Notebook',
|
||||||
name: 'Test Notebook'
|
name: 'Test Notebook'
|
||||||
|
@ -28,11 +28,11 @@ import percySnapshot from '@percy/playwright';
|
|||||||
|
|
||||||
import { createNotification } from '../../appActions.js';
|
import { createNotification } from '../../appActions.js';
|
||||||
import { expect, scanForA11yViolations, test } from '../../avpFixtures.js';
|
import { expect, scanForA11yViolations, test } from '../../avpFixtures.js';
|
||||||
import { VISUAL_URL } from '../../constants.js';
|
import { VISUAL_FIXED_URL } from '../../constants.js';
|
||||||
|
|
||||||
test.describe('Visual - Notifications @a11y', () => {
|
test.describe('Visual - Notifications @a11y', () => {
|
||||||
test.beforeEach(async ({ page }) => {
|
test.beforeEach(async ({ page }) => {
|
||||||
await page.goto(VISUAL_URL, { waitUntil: 'domcontentloaded' });
|
await page.goto(VISUAL_FIXED_URL, { waitUntil: 'domcontentloaded' });
|
||||||
});
|
});
|
||||||
|
|
||||||
test('Alert Levels and Notification List Modal', async ({ page, theme }) => {
|
test('Alert Levels and Notification List Modal', async ({ page, theme }) => {
|
||||||
|
@ -25,7 +25,7 @@ import fs from 'fs';
|
|||||||
|
|
||||||
import { createDomainObjectWithDefaults, createPlanFromJSON } from '../../appActions.js';
|
import { createDomainObjectWithDefaults, createPlanFromJSON } from '../../appActions.js';
|
||||||
import { test } from '../../avpFixtures.js';
|
import { test } from '../../avpFixtures.js';
|
||||||
import { VISUAL_URL } from '../../constants.js';
|
import { VISUAL_FIXED_URL } from '../../constants.js';
|
||||||
import {
|
import {
|
||||||
createTimelistWithPlanAndSetActivityInProgress,
|
createTimelistWithPlanAndSetActivityInProgress,
|
||||||
getFirstActivity,
|
getFirstActivity,
|
||||||
@ -64,7 +64,7 @@ test.describe('Visual - Timelist progress bar @clock', () => {
|
|||||||
|
|
||||||
test.describe('Visual - Planning', () => {
|
test.describe('Visual - Planning', () => {
|
||||||
test.beforeEach(async ({ page }) => {
|
test.beforeEach(async ({ page }) => {
|
||||||
await page.goto(VISUAL_URL, { waitUntil: 'domcontentloaded' });
|
await page.goto(VISUAL_FIXED_URL, { waitUntil: 'domcontentloaded' });
|
||||||
});
|
});
|
||||||
|
|
||||||
test('Plan View', async ({ page, theme }) => {
|
test('Plan View', async ({ page, theme }) => {
|
||||||
@ -72,17 +72,35 @@ test.describe('Visual - Planning', () => {
|
|||||||
name: 'Plan Visual Test',
|
name: 'Plan Visual Test',
|
||||||
json: examplePlanSmall2
|
json: examplePlanSmall2
|
||||||
});
|
});
|
||||||
|
|
||||||
await setBoundsToSpanAllActivities(page, examplePlanSmall2, plan.url);
|
await setBoundsToSpanAllActivities(page, examplePlanSmall2, plan.url);
|
||||||
await percySnapshot(page, `Plan View (theme: ${theme})`);
|
await percySnapshot(page, `Plan View (theme: ${theme})`);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('Resize Plan View @2p', async ({ browser, theme }) => {
|
||||||
|
// need to set viewport to null to allow for resizing
|
||||||
|
const newContext = await browser.newContext({
|
||||||
|
viewport: null
|
||||||
|
});
|
||||||
|
const newPage = await newContext.newPage();
|
||||||
|
|
||||||
|
await newPage.goto(VISUAL_FIXED_URL, { waitUntil: 'domcontentloaded' });
|
||||||
|
const plan = await createPlanFromJSON(newPage, {
|
||||||
|
name: 'Plan Visual Test',
|
||||||
|
json: examplePlanSmall2
|
||||||
|
});
|
||||||
|
|
||||||
|
await setBoundsToSpanAllActivities(newPage, examplePlanSmall2, plan.url);
|
||||||
|
// resize the window
|
||||||
|
await newPage.setViewportSize({ width: 800, height: 600 });
|
||||||
|
await percySnapshot(newPage, `Plan View resized (theme: ${theme})`);
|
||||||
|
});
|
||||||
|
|
||||||
test('Plan View w/ draft status', async ({ page, theme }) => {
|
test('Plan View w/ draft status', async ({ page, theme }) => {
|
||||||
const plan = await createPlanFromJSON(page, {
|
const plan = await createPlanFromJSON(page, {
|
||||||
name: 'Plan Visual Test (Draft)',
|
name: 'Plan Visual Test (Draft)',
|
||||||
json: examplePlanSmall2
|
json: examplePlanSmall2
|
||||||
});
|
});
|
||||||
await page.goto(VISUAL_URL, { waitUntil: 'domcontentloaded' });
|
await page.goto(VISUAL_FIXED_URL, { waitUntil: 'domcontentloaded' });
|
||||||
await setDraftStatusForPlan(page, plan);
|
await setDraftStatusForPlan(page, plan);
|
||||||
|
|
||||||
await setBoundsToSpanAllActivities(page, examplePlanSmall2, plan.url);
|
await setBoundsToSpanAllActivities(page, examplePlanSmall2, plan.url);
|
||||||
@ -92,7 +110,7 @@ test.describe('Visual - Planning', () => {
|
|||||||
|
|
||||||
test.describe('Visual - Gantt Chart', () => {
|
test.describe('Visual - Gantt Chart', () => {
|
||||||
test.beforeEach(async ({ page }) => {
|
test.beforeEach(async ({ page }) => {
|
||||||
await page.goto(VISUAL_URL, { waitUntil: 'domcontentloaded' });
|
await page.goto(VISUAL_FIXED_URL, { waitUntil: 'domcontentloaded' });
|
||||||
});
|
});
|
||||||
test('Gantt Chart View', async ({ page, theme }) => {
|
test('Gantt Chart View', async ({ page, theme }) => {
|
||||||
const ganttChart = await createDomainObjectWithDefaults(page, {
|
const ganttChart = await createDomainObjectWithDefaults(page, {
|
||||||
@ -135,7 +153,7 @@ test.describe('Visual - Gantt Chart', () => {
|
|||||||
|
|
||||||
await setDraftStatusForPlan(page, plan);
|
await setDraftStatusForPlan(page, plan);
|
||||||
|
|
||||||
await page.goto(VISUAL_URL, { waitUntil: 'domcontentloaded' });
|
await page.goto(VISUAL_FIXED_URL, { waitUntil: 'domcontentloaded' });
|
||||||
|
|
||||||
await setBoundsToSpanAllActivities(page, examplePlanSmall2, ganttChart.url);
|
await setBoundsToSpanAllActivities(page, examplePlanSmall2, ganttChart.url);
|
||||||
await percySnapshot(page, `Gantt Chart View w/ draft status (theme: ${theme})`);
|
await percySnapshot(page, `Gantt Chart View w/ draft status (theme: ${theme})`);
|
||||||
|
@ -28,13 +28,13 @@ import percySnapshot from '@percy/playwright';
|
|||||||
|
|
||||||
import { createDomainObjectWithDefaults } from '../../appActions.js';
|
import { createDomainObjectWithDefaults } from '../../appActions.js';
|
||||||
import { expect, scanForA11yViolations, test } from '../../avpFixtures.js';
|
import { expect, scanForA11yViolations, test } from '../../avpFixtures.js';
|
||||||
import { VISUAL_URL } from '../../constants.js';
|
import { VISUAL_FIXED_URL } from '../../constants.js';
|
||||||
|
|
||||||
test.describe('Grand Search @a11y', () => {
|
test.describe('Grand Search @a11y', () => {
|
||||||
let conditionWidget;
|
let conditionWidget;
|
||||||
let displayLayout;
|
let displayLayout;
|
||||||
test.beforeEach(async ({ page }) => {
|
test.beforeEach(async ({ page }) => {
|
||||||
await page.goto(VISUAL_URL, { waitUntil: 'domcontentloaded' });
|
await page.goto(VISUAL_FIXED_URL, { waitUntil: 'domcontentloaded' });
|
||||||
|
|
||||||
displayLayout = await createDomainObjectWithDefaults(page, {
|
displayLayout = await createDomainObjectWithDefaults(page, {
|
||||||
type: 'Display Layout',
|
type: 'Display Layout',
|
||||||
|
@ -91,7 +91,7 @@ test.describe('Flexible Layout styling @a11y', () => {
|
|||||||
setBorderColor,
|
setBorderColor,
|
||||||
setBackgroundColor,
|
setBackgroundColor,
|
||||||
setTextColor,
|
setTextColor,
|
||||||
page.getByLabel('StackedPlot1 Frame')
|
page.getByRole('group', { name: 'StackedPlot1 Frame' })
|
||||||
);
|
);
|
||||||
|
|
||||||
await percySnapshot(
|
await percySnapshot(
|
||||||
|
@ -23,14 +23,14 @@
|
|||||||
import percySnapshot from '@percy/playwright';
|
import percySnapshot from '@percy/playwright';
|
||||||
|
|
||||||
import { createDomainObjectWithDefaults } from '../../appActions.js';
|
import { createDomainObjectWithDefaults } from '../../appActions.js';
|
||||||
import { VISUAL_URL } from '../../constants.js';
|
import { VISUAL_FIXED_URL } from '../../constants.js';
|
||||||
import { expect, test } from '../../pluginFixtures.js';
|
import { expect, test } from '../../pluginFixtures.js';
|
||||||
|
|
||||||
test.describe('Visual - Telemetry Views', () => {
|
test.describe('Visual - Telemetry Views', () => {
|
||||||
let telemetry;
|
let telemetry;
|
||||||
|
|
||||||
test.beforeEach(async ({ page }) => {
|
test.beforeEach(async ({ page }) => {
|
||||||
await page.goto(VISUAL_URL, { waitUntil: 'domcontentloaded' });
|
await page.goto(VISUAL_FIXED_URL, { waitUntil: 'domcontentloaded' });
|
||||||
|
|
||||||
// Create SWG inside of LAD Table
|
// Create SWG inside of LAD Table
|
||||||
telemetry = await createDomainObjectWithDefaults(page, {
|
telemetry = await createDomainObjectWithDefaults(page, {
|
||||||
@ -53,11 +53,11 @@ test.describe('Visual - Telemetry Views', () => {
|
|||||||
await page.goto(telemetry.url, { waitUntil: 'domcontentloaded' });
|
await page.goto(telemetry.url, { waitUntil: 'domcontentloaded' });
|
||||||
|
|
||||||
//Click this button to see telemetry display options
|
//Click this button to see telemetry display options
|
||||||
await page.getByRole('button', { name: 'Plot' }).click();
|
await page.getByLabel('Open the View Switcher Menu').click();
|
||||||
await page.getByLabel('Telemetry Table').click();
|
await page.getByLabel('Telemetry Table').click();
|
||||||
|
|
||||||
//Get Table View in place
|
//Get Table View in place
|
||||||
expect(await page.getByLabel('Expand Columns')).toBeInViewport();
|
await expect(page.getByLabel('Expand Columns')).toBeInViewport();
|
||||||
|
|
||||||
await percySnapshot(page, `Default Telemetry Table View (theme: ${theme})`);
|
await percySnapshot(page, `Default Telemetry Table View (theme: ${theme})`);
|
||||||
|
|
||||||
|
@ -55,6 +55,7 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
|
const ONE_HOUR = 60 * 60 * 1000;
|
||||||
export default {
|
export default {
|
||||||
inject: ['openmct', 'domainObject'],
|
inject: ['openmct', 'domainObject'],
|
||||||
data() {
|
data() {
|
||||||
@ -77,6 +78,10 @@ export default {
|
|||||||
selectItem(item, event) {
|
selectItem(item, event) {
|
||||||
event.stopPropagation();
|
event.stopPropagation();
|
||||||
const bounds = this.openmct.time.getBounds();
|
const bounds = this.openmct.time.getBounds();
|
||||||
|
const otherBounds = {
|
||||||
|
start: bounds.start - ONE_HOUR,
|
||||||
|
end: bounds.end + ONE_HOUR
|
||||||
|
};
|
||||||
const selection = [
|
const selection = [
|
||||||
{
|
{
|
||||||
element: this.$el,
|
element: this.$el,
|
||||||
@ -88,6 +93,9 @@ export default {
|
|||||||
icon: item.type.cssClass
|
icon: item.type.cssClass
|
||||||
},
|
},
|
||||||
dataRanges: [
|
dataRanges: [
|
||||||
|
{
|
||||||
|
bounds: otherBounds
|
||||||
|
},
|
||||||
{
|
{
|
||||||
bounds
|
bounds
|
||||||
}
|
}
|
||||||
|