Compare commits

..

9 Commits

Author SHA1 Message Date
49cf23b7e9 form no work 2023-12-28 21:47:51 -08:00
37a2b05612 chore: first fixes 2023-12-28 21:47:43 -08:00
3c89236da2 fix import 2023-12-27 21:53:16 -08:00
827ec5690f use stable object 2023-12-27 21:53:07 -08:00
5c8355c7f8 remove deploysentinel 2023-12-27 21:52:48 -08:00
0f1d80c6b6 Merge branch 'master' of https://github.com/nasa/openmct into eslint-playwright-changes 2023-12-27 21:48:57 -08:00
1e8bb5fd8b chore: add new rules and fix overrides 2023-12-27 20:56:23 -08:00
e1fffaf39a chore:bump eslint playwright 2023-12-27 20:56:06 -08:00
6d47b5bc14 chore: folder changes 2023-12-27 20:55:49 -08:00
1017 changed files with 7267 additions and 30659 deletions

View File

@ -1,31 +1,57 @@
version: 2.1
orbs:
node: circleci/node@5.2.0
browser-tools: circleci/browser-tools@1.3.0
executors:
pw-focal-development:
docker:
- image: mcr.microsoft.com/playwright:v1.42.1-focal
- image: mcr.microsoft.com/playwright:v1.39.0-focal
environment:
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_LOGLEVEL: 'debug' # Enable DEBUG level logging for Percy (Issue: https://github.com/nasa/openmct/issues/5742)
PERCY_PARALLEL_TOTAL: 2
ubuntu:
machine:
image: ubuntu-2204:current
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:
build_and_install:
description: 'All steps used to build and install.'
description: 'All steps used to build and install. Will use cache if found'
parameters:
node-version:
type: string
steps:
- checkout
- restore_cache_cmd:
node-version: << parameters.node-version >>
- node/install:
node-version: << parameters.node-version >>
- node/install-packages
- run: npm install --no-audit --progress=false
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:
description: 'Track important packages and files'
steps:
@ -45,6 +71,9 @@ commands:
steps:
- run: npm run cov:e2e:report || true
- run: npm run cov:e2e:<<parameters.suite>>:publish
orbs:
node: circleci/node@5.1.0
browser-tools: circleci/browser-tools@1.3.0
jobs:
npm-audit:
parameters:
@ -76,12 +105,10 @@ jobs:
node-version: <<parameters.node-version>>
- browser-tools/install-chrome:
replace-existing: false
- run:
command: |
mkdir -p dist/reports/tests/
TESTFILES=$(circleci tests glob "src/**/*Spec.js")
echo "$TESTFILES" | circleci tests run --command="xargs npm run test" --verbose
- run: npm run test
- run: npm run cov:unit:publish
- save_cache_cmd:
node-version: <<parameters.node-version>>
- store_test_results:
path: dist/reports/tests/
- store_artifacts:
@ -96,7 +123,7 @@ jobs:
suite: #stable or full
type: string
executor: pw-focal-development
parallelism: 7
parallelism: 6
steps:
- build_and_install:
node-version: lts/hydrogen
@ -105,11 +132,7 @@ jobs:
equal: ['full', <<parameters.suite>>]
steps:
- run: npx playwright install chrome-beta
- run:
command: |
mkdir test-results
TESTFILES=$(circleci tests glob "e2e/**/*.spec.js")
echo "$TESTFILES" | circleci tests run --command="xargs npm run test:e2e:<<parameters.suite>>" --verbose --split-by=timings
- run: SHARD="$((${CIRCLE_NODE_INDEX}+1))"; npm run test:e2e:<<parameters.suite>> -- --shard=${SHARD}/${CIRCLE_NODE_TOTAL}
- when:
condition:
equal: [42, 42] # Always run codecov reports regardless of test failure https://discuss.circleci.com/t/make-custom-command-run-always-with-when-always/38957/2
@ -129,37 +152,12 @@ jobs:
equal: [42, 42] # Always generate version artifacts regardless of test failure https://discuss.circleci.com/t/make-custom-command-run-always-with-when-always/38957/2
steps:
- generate_and_store_version_and_filesystem_artifacts
e2e-mobile:
executor: pw-focal-development
steps:
- build_and_install:
node-version: lts/hydrogen
- run: npm run test:e2e:mobile
- when:
condition:
equal: [42, 42] # Always run codecov reports regardless of test failure https://discuss.circleci.com/t/make-custom-command-run-always-with-when-always/38957/2
steps:
- generate_e2e_code_cov_report:
suite: full
- store_test_results:
path: test-results/results.xml
- store_artifacts:
path: test-results
- store_artifacts:
path: coverage
- store_artifacts:
path: html-test-results
- when:
condition:
equal: [42, 42] # Always generate version artifacts regardless of test failure https://discuss.circleci.com/t/make-custom-command-run-always-with-when-always/38957/2
steps:
- generate_and_store_version_and_filesystem_artifacts
e2e-couchdb:
executor: ubuntu
steps:
- build_and_install:
node-version: lts/hydrogen
- run: npx playwright@1.42.1 install #Necessary for bare ubuntu machine
- run: npx playwright@1.39.0 install #Necessary for bare ubuntu machine
- run: |
export $(cat src/plugins/persistence/couch/.env.ci | xargs)
docker-compose -f src/plugins/persistence/couch/couchdb-compose.yaml up --detach
@ -221,15 +219,14 @@ 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
steps:
- generate_and_store_version_and_filesystem_artifacts
visual-a11y:
visual-a11y-tests:
parameters:
suite:
type: string # ci or full
executor: pw-focal-development
parallelism: 2
steps:
- build_and_install:
node-version: lts/iron
node-version: lts/hydrogen
- run: npm run test:e2e:visual:<<parameters.suite>>
- store_test_results:
path: test-results/results.xml
@ -242,7 +239,6 @@ jobs:
equal: [42, 42] # Always generate version artifacts regardless of test failure https://discuss.circleci.com/t/make-custom-command-run-always-with-when-always/38957/2
steps:
- generate_and_store_version_and_filesystem_artifacts
workflows:
overall-circleci-commit-status: #These jobs run on every commit
jobs:
@ -255,9 +251,10 @@ workflows:
- e2e-test:
name: e2e-stable
suite: stable
- e2e-mobile
- visual-a11y:
name: visual-a11y-ci
- mem-test
- perf-test
- visual-a11y-tests:
name: visual-test-ci
suite: ci
the-nightly: #These jobs do not run on PRs, but against master at night
@ -273,11 +270,10 @@ workflows:
- e2e-test:
name: e2e-full-nightly
suite: full
- e2e-mobile
- perf-test
- mem-test
- visual-a11y:
name: visual-a11y-nightly
- perf-test
- visual-a11y-tests:
name: visual-test-nightly
suite: full
- e2e-couchdb
triggers:

View File

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

View File

@ -1,13 +1,10 @@
const LEGACY_FILES = ['example/**'];
/** @type {import('eslint').Linter.Config} */
const config = {
module.exports = {
env: {
browser: true,
es2024: true,
es6: true,
jasmine: true,
node: true,
worker: true,
serviceworker: true
amd: true
},
globals: {
_: 'readonly'
@ -26,11 +23,10 @@ const config = {
parser: '@babel/eslint-parser',
requireConfigFile: false,
allowImportExportEverywhere: true,
ecmaVersion: 'latest',
ecmaVersion: 2015,
ecmaFeatures: {
impliedStrict: true
},
sourceType: 'module'
}
},
rules: {
'simple-import-sort/imports': 'warn',
@ -156,7 +152,7 @@ const config = {
cases: {
pascalCase: true
},
ignore: ['^.*\\.(js|cjs|mjs)$']
ignore: ['^.*\\.js$']
}
],
'vue/first-attribute-linebreak': 'error',
@ -183,5 +179,3 @@ const config = {
}
]
};
module.exports = config;

View File

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

View File

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

5
.github/release.yml vendored
View File

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

View File

@ -28,7 +28,7 @@ jobs:
restore-keys: |
${{ runner.os }}-node-
- run: npm ci --no-audit --progress=false
- run: npm install --cache ~/.npm --no-audit --progress=false
- name: Login to DockerHub
uses: docker/login-action@v3
@ -37,7 +37,7 @@ jobs:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
- run: npx playwright@1.42.1 install
- run: npx playwright@1.39.0 install
- name: Start CouchDB Docker Container and Init with Setup Scripts
run: |
@ -47,8 +47,9 @@ jobs:
bash src/plugins/persistence/couch/setup-couchdb.sh
bash src/plugins/persistence/couch/replace-localstorage-with-couchdb-indexhtml.sh
- name: Run CouchDB Tests
- name: Run CouchDB Tests and publish to deploysentinel
env:
DEPLOYSENTINEL_API_KEY: ${{ secrets.DEPLOYSENTINEL_API_KEY }}
COMMIT_INFO_SHA: ${{github.event.pull_request.head.sha }}
run: npm run test:e2e:couchdb

View File

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

View File

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

View File

@ -33,9 +33,9 @@ jobs:
restore-keys: |
${{ runner.os }}-node-
- run: npx playwright@1.42.1 install
- run: npx playwright@1.39.0 install
- run: npx playwright install chrome-beta
- run: npm ci --no-audit --progress=false
- run: npm install --cache ~/.npm --no-audit --progress=false
- run: npm run test:e2e:full -- --max-failures=40
- run: npm run cov:e2e:report || true
- shell: bash

View File

@ -15,7 +15,7 @@ jobs:
- uses: actions/setup-node@v4
with:
node-version: lts/hydrogen
- run: npm ci
- run: npm install
- run: |
echo "//registry.npmjs.org/:_authToken=$NODE_AUTH_TOKEN" >> ~/.npmrc
npm whoami
@ -31,7 +31,7 @@ jobs:
with:
node-version: lts/hydrogen
registry-url: https://registry.npmjs.org/
- run: npm ci
- run: npm install
- run: npm publish --access=public --tag unstable
env:
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}

View File

@ -45,7 +45,7 @@ jobs:
restore-keys: |
${{ runner.os }}-${{ matrix.node_version }}-
- run: npm ci --no-audit --progress=false
- run: npm install --cache ~/.npm --no-audit --progress=false
- run: npm test

View File

@ -5,8 +5,6 @@ on:
types:
- labeled
- unlabeled
- milestoned
- demilestoned
- opened
- reopened
- synchronize

7
.gitignore vendored
View File

@ -15,9 +15,6 @@
*.idea
*.iml
# VSCode
.vscode/settings.json
# Build output
target
dist
@ -48,5 +45,5 @@ index.html.bak
coverage
codecov
# Don't commit MacOS screenshots
*-darwin.png
# :(
package-lock.json

View File

@ -22,3 +22,9 @@
!index.html
!openmct.js
!SECURITY.md
# Add e2e tests to npm package
!/e2e/**/*
# ... except our test-data folder files.
/e2e/test-data/*.json

3
.npmrc
View File

@ -2,3 +2,6 @@ loglevel=warn
#Prevent folks from ignoring an important error when building from source
engine-strict=true
# Dont include lockfile
package-lock=false

View File

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

View File

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

View File

@ -1,12 +1,17 @@
/* global module */
/*
This file extends the webpack.dev.mjs config to add babel istanbul coverage.
This file extends the webpack.dev.js config to add babel istanbul coverage.
OpenMCT Continuous Integration servers use this configuration to add code coverage
information to pull requests.
*/
import config from './webpack.dev.mjs';
const config = require('./webpack.dev');
// 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.module.rules.push({
@ -16,6 +21,7 @@ config.module.rules.push({
loader: 'babel-loader',
options: {
retainLines: true,
// eslint-disable-next-line no-undef
plugins: [
[
'babel-plugin-istanbul',
@ -28,4 +34,4 @@ config.module.rules.push({
}
});
export default config;
module.exports = config;

View File

@ -1,17 +1,18 @@
/* global __dirname module */
/*
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.
If OpenMCT is to be used for a production server, use webpack.prod.mjs instead.
If OpenMCT is to be used for a production server, use webpack.prod.js instead.
*/
import { fileURLToPath } from 'node:url';
const path = require('path');
const webpack = require('webpack');
const { merge } = require('webpack-merge');
import path from 'path';
import webpack from 'webpack';
import { merge } from 'webpack-merge';
const common = require('./webpack.common');
const projectRootDir = path.resolve(__dirname, '..');
import common from './webpack.common.mjs';
export default merge(common, {
module.exports = merge(common, {
mode: 'development',
watchOptions: {
// Since we use require.context, webpack is watching the entire directory.
@ -41,7 +42,7 @@ export default merge(common, {
},
watchFiles: ['**/*.css'],
static: {
directory: fileURLToPath(new URL('../dist', import.meta.url)),
directory: path.join(__dirname, '..', '/dist'),
publicPath: '/dist',
watch: false
}

22
.webpack/webpack.prod.js Normal file
View File

@ -0,0 +1,22 @@
/* global __dirname module */
/*
This configuration should be used for production installs.
It is the default webpack configuration.
*/
const path = require('path');
const webpack = require('webpack');
const { merge } = require('webpack-merge');
const common = require('./webpack.common');
const projectRootDir = path.resolve(__dirname, '..');
module.exports = merge(common, {
mode: 'production',
plugins: [
new webpack.DefinePlugin({
__OPENMCT_ROOT_RELATIVE__: '""'
})
],
devtool: 'source-map'
});

View File

@ -1,19 +0,0 @@
/*
This configuration should be used for production installs.
It is the default webpack configuration.
*/
import webpack from 'webpack';
import { merge } from 'webpack-merge';
import common from './webpack.common.mjs';
export default merge(common, {
mode: 'production',
plugins: [
new webpack.DefinePlugin({
__OPENMCT_ROOT_RELATIVE__: '""'
})
],
devtool: 'source-map'
});

10
API.md
View File

@ -1305,16 +1305,6 @@ 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
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).

View File

@ -1,6 +1,6 @@
# Open MCT License
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, Copyright (c) 2014-2023, 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.

View File

@ -16,6 +16,8 @@ The [CodeQL GitHub Actions workflow](https://github.com/nasa/openmct/blob/master
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
Static analysis is run for every push on the master branch and every pull request on all branches in Github Actions.

View File

@ -63,7 +63,7 @@ Once the file is generated, it can be published to codecov with
### e2e
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.mjs` 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.js` and the `babel-plugin-istanbul` plugin to generate code coverage during e2e test execution using our custom [baseFixture](./baseFixtures.js).
1. During testcase execution, each e2e shard will generate its piece of the larger coverage suite. **This coverage file is not merged**. The raw coverage file is stored in a `.nyc_report` directory.
1. [nyc](https://github.com/istanbuljs/nyc) converts this directory into a `lcov` file with the following command `npm run cov:e2e:report`
1. Most of the tests are run in the '@stable' configuration and focus on chrome/ubuntu at a single resolution. This coverage is published to codecov with `npm run cov:e2e:stable:publish`.
@ -85,20 +85,19 @@ There are a few reasons that your GitHub PR could be failing beyond simple faile
* Not all required checks are run per commit. You may need to manually trigger addition GitHub checks with a `pr:<label>` label added to your PR.
### Flaky tests
(CircleCI's test insights feature)[https://circleci.com/blog/introducing-test-insights-with-flaky-test-detection/] collects historical data about the individual test results for both unit and e2e tests. Note: only a 14 day window of flake is available.
There are two ways to know if a test on your branch is historically flaky:
1. `deploysentinel`'s PR comment bot to give an accurate and historical view of e2e flakiness. Check your PR for a view of the test failures and flakes (with link to the failing test). Note: only a 7 day window of flake is available.
2. (CircleCI's test insights feature)[https://circleci.com/blog/introducing-test-insights-with-flaky-test-detection/] collects historical data about the individual test results for both unit and e2e tests. Note: only a 14 day window of flake is available.
### Local=Pass and CI=Fail
Although rare, it is possible that your test can pass locally but fail in CI.
### Reset your workspace
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:
```sh
nvm use
npm run clean
npm install
```
#### Busting Cache
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:
1. Navigate to the branch in Circle CI believed to have stale cache.
1. Click on the 'Trigger Pipeline' button.
1. Add Parameter -> Parameter Type = boolean , Name = BUST_CACHE ,Value = true
1. Click 'Trigger Pipeline'
#### Run tests in the same container as CI

View File

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

View File

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

View File

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

View File

@ -1,30 +0,0 @@
# Release of NASA Open MCT NPM Package
This document outlines the process and key considerations for releasing a new version of the NASA Open MCT project as an NPM (Node Package Manager) package.
## 1. Pre-requisites
Before releasing a new version of the NASA Open MCT NPM package, ensure all dependencies are updated, and comprehensive tests are performed. This ensures compatibility and performance of the Open MCT within the Node.js ecosystem.
## 2. Versioning
Versioning is a critical step for package release. The Open MCT team follows [Semantic Versioning (SemVer)](https://semver.org) that consists of three major components: MAJOR.MINOR.PATCH. These ensure a structured process for updating, bug fixes, backward compatibility, and software progress.
## 3. Changelog Maintenance
A comprehensive changelog file, `CHANGELOG.md`, documents any changes, adding a high level of transparencies for anyone desiring to look into the status of new and past progress. It includes the summation of any major new enhancements, changes, bug fixes, and the credits to the users responsible for each unique progress.
## 4. Notable Changes Labels on GitHub PRs
For the Open MCT package, we leverage GitHub's Pull Request (PR) mechanisms extensively, with three important PR labels dedicated to signifying 'notable_changes':
- **Breaking Change** Highlights the integration of changes that are suspected to break, or without a doubt will break, backward compatibility. These should signal to users the upgrade might be seamless only if dependency and integration factors are properly managed, if not, one should expect to manage atypical technical snags.
- **API change** Signifies when a contribution makes any complete or under layer changes to the communication or its supporting access processes. This label flags required see-through insight on how the web-based control panel sees and manipulates any value and or network logs.
- **Default Behavior Change:** In the incident an update either adjusts a form to or integrates a not previously kept setting or plugin. i.e. autoscale is enabled by default when working with plots.
## 6. Community & Contributions
A flat community and the rounded center are kept in continuous celebration, with the given station open for two open-specifying dialogues, research, and all-for development probing. State the ownership for a handed looped, a welcome for even structure-core and architectural draft and impend.
Thank you for your collaboration and commitment to moving the project onto a text big club.

View File

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

18
e2e/.eslintrc.js Normal file
View File

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

View File

@ -1,7 +0,0 @@
*
!appActions.js
!baseFixtures.js
!pluginFixtures.js
!avpFixtures.js
!index.js
!*.md

View File

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

View File

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

View File

@ -78,7 +78,6 @@ To read about how to write a good visual test, please see [How to write a great
- `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.
#### 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).
@ -90,17 +89,16 @@ At present, we are using percy with two configuration files: `./e2e/.percy.night
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
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
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.
#### Open MCT's implementation
- Our Snapshot tests receive a `@snapshot` tag.
@ -111,7 +109,7 @@ For those interested in the mechanics of snapshot testing with Playwright, you c
// from our package.json or circleCI configuration file
docker run --rm --network host -v $(pwd):/work/ -w /work/ -it mcr.microsoft.com/playwright:v{X.X.X}-focal /bin/bash
npm install
npm run test:e2e:checksnapshots
npx playwright test --config=e2e/playwright-ci.config.js --project=chrome --grep @snapshot
```
### Updating Snapshots
@ -120,6 +118,14 @@ 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.
MacOS
```
npm run test:e2e:updatesnapshots
```
Linux/CI
```sh
// Replace {X.X.X} with the current Playwright version
// from our package.json or circleCI configuration file
@ -128,12 +134,6 @@ npm install
npm run test:e2e:updatesnapshots
```
Once that's done, you'll need to run the following to verify that the changes do not cause more problems:
```sh
npm run test:e2e:checksnapshots
```
## Automated Accessibility (a11y) Testing
Open MCT incorporates accessibility testing through two primary methods to ensure its compliance with accessibility standards:
@ -167,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:
`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/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/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.
`./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.
`./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.
`./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.
These tests are expected to become blocking and gating with assertions as we extend the capabilities of Playwright.
@ -223,7 +223,7 @@ Current list of test tags:
|Test Tag|Description|
|:-:|-|
|`@mobile` | Test case or test suite is compatible with Playwright's iPad support and Open MCT's read-only mobile view (i.e. no create button).|
|`@ipad` | Test case or test suite is compatible with Playwright's iPad support and Open MCT's read-only mobile view (i.e. no create button).|
|`@a11y` | Test case or test suite to execute playwright-axe accessibility checks and generate a11y reports.|
|`@gds` | Denotes a GDS Test Case used in the VIPER Mission.|
|`@addInit` | Initializes the browser with an injected and artificial state. Useful for loading non-default plugins. Likely will not work outside of `npm start`.|
@ -232,7 +232,6 @@ Current list of test tags:
|`@unstable` | A new test or test which is known to be flaky.|
|`@2p` | Indicates that multiple users are involved, or multiple tabs/pages are used. Useful for testing multi-user interactivity.|
|`@generatedata` | Indicates that a test is used to generate testdata or test the generated test data. Usually to be associated with localstorage, but this may grow over time.|
|`@clock` | A test which modifies the clock. These have expanded out of the visual tests and into the functional tests.
### Continuous Integration
@ -324,17 +323,9 @@ In terms of operating system testing, we're only limited by what the CI provider
#### **Mobile**
We have a Mission-need to support iPad and mobile devices. To run our test suites with mobile devices, please see our `playwright-mobile.config.js` projects.
We have the Mission-need to support iPad. To run our iPad suite, please see our `playwright-*.config.js` with the 'iPad' project.
In general, our test suite is not designed to run against mobile devices as the mobile experience is a focused version of the application. Core functionality is missing (chiefly the 'Create' button). To bypass the object creation, we leverage the `storageState` properties for starting the mobile tests with localstorage.
For now, the mobile tests will exist in the /tests/mobile/ suites and be executed with the
```sh
npm run test:e2e:mobile
```
command.
In general, our test suite is not designed to run against mobile devices as the mobile experience is a focused version of the application. Core functionality is missing (chiefly the 'Create' button) and so this will likely turn into a separate suite.
#### **Skipping or executing tests based on browser, os, and/os browser version:**
@ -373,7 +364,6 @@ 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.
#### 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. 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.
@ -381,7 +371,6 @@ 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.
#### 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:
```js
@ -398,7 +387,6 @@ By adhering to this principle, we can create tests that are both robust and refl
This ensures that your changes will be picked up with large refactors.
##### 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. 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:
@ -419,6 +407,7 @@ By adhering to this principle, we can create tests that are both robust and refl
});
```
### How to write a great test
- Avoid using css locators to find elements to the page. Use modern web accessible locators like `getByRole`
@ -443,27 +432,22 @@ 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.
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
- 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:
- `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:
```js
await percySnapshot(page, `Tree Pane w/ single level expanded (theme: ${theme})`, {
scope: treePane
});
```
- 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:
```js
//<Some interesting state>
await percySnapshot(page, `Before object expanded (theme: ${theme})`);
@ -496,7 +480,7 @@ The following contains a list of tips and tricks which don't exactly fit into a
It is possible to override the browser's clock in order to control time-based elements. Since this can cause unwanted behavior (i.e. Tree not rendering), only use this sparingly. To do this, use the `overrideClock` fixture as such:
```js
import { test, expect } from '../../pluginFixtures.js';
const { test, expect } = require('../../pluginFixtures.js');
test.describe('foo test suite', () => {
@ -513,35 +497,11 @@ test.describe('foo test suite', () => {
});
});
```
More info and options for `overrideClock` can be found in [baseFixtures.js](baseFixtures.js)
- Working with multiple pages
There are instances where multiple browser pages will needed to verify multi-page or multi-tab application behavior. Make sure to use the `@2p` annotation as well as name each page appropriately: i.e. `page1` and `page2` or `tab1` and `tab2` depending on the intended use case. Generally pages should be used unless testing `sharedWorker` code, specifically.
- 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
Test Reporting is done through official Playwright reporters and the CI Systems which execute them.
@ -617,7 +577,6 @@ A single e2e test in Open MCT is extended to run:
### Writing Tests
Playwright provides 3 supported methods of debugging and authoring tests:
- A 'watch mode' for running tests locally and debugging on the fly
- A 'debug mode' for debugging tests and writing assertions against tests
- A 'VSCode plugin' for debugging tests within the VSCode IDE.

View File

@ -1,5 +1,5 @@
/*****************************************************************************
* Open MCT, Copyright (c) 2014-2024, United States Government
* Open MCT, Copyright (c) 2014-2023, United States Government
* as represented by the Administrator of the National Aeronautics and Space
* Administration. All rights reserved.
*
@ -54,9 +54,9 @@
* @property {import('../src/api/notifications/NotificationAPI').NotificationOptions} [notificationOptions] additional options
*/
import { expect } from '@playwright/test';
import { Buffer } from 'buffer';
import { v4 as genUuid } from 'uuid';
const Buffer = require('buffer').Buffer;
const genUuid = require('uuid').v4;
const { expect } = require('@playwright/test');
/**
* This common function creates a domain object with the default options. It is the preferred way of creating objects
@ -84,12 +84,10 @@ async function createDomainObjectWithDefaults(
await page.getByRole('button', { name: 'Create' }).click();
// Click the object specified by 'type'
await page.click(`li[role='menuitem']:text("${type}")`);
await page.getByRole('menuitem', { name: new RegExp(`${type}`) }).click();
// Modify the name input field of the domain object to accept 'name'
const nameInput = page.locator('form[name="mctForm"] .first input[type="text"]');
await nameInput.fill('');
await nameInput.fill(name);
await page.getByLabel('Title', { exact: true }).fill(name);
if (page.testNotes) {
// Fill the "Notes" section with information about the
@ -284,7 +282,7 @@ async function navigateToObjectWithFixedTimeBounds(page, url, start, end) {
*/
async function openObjectTreeContextMenu(page, url) {
await page.goto(url);
await page.getByLabel('Show selected item in tree').click();
await page.click('button[title="Show selected item in tree"]');
await page.locator('.is-navigated-object').click({
button: 'right'
});
@ -392,8 +390,6 @@ async function setTimeConductorMode(page, isFixedTimespan = true) {
await page.getByRole('menuitem', { name: /Real-Time/ }).click();
await page.waitForURL(/tc\.mode=local/);
}
//dismiss the time conductor popup
await page.getByLabel('Discard changes and close time popup').click();
}
/**
@ -507,14 +503,15 @@ async function setTimeConductorBounds(page, startDate, endDate) {
* @param {string} startDate
* @param {string} endDate
*/
async function setIndependentTimeConductorBounds(page, { start, end }) {
// Activate Independent Time Conductor
await page.getByLabel('Enable Independent Time Conductor').click();
async function setIndependentTimeConductorBounds(page, startDate, endDate) {
// Activate Independent Time Conductor in Fixed Time Mode
await page.getByRole('switch').click();
// Bring up the time conductor popup
await page.getByLabel('Independent Time Conductor Settings').click();
await page.click('.c-conductor-holder--compact .c-compact-tc');
await expect(page.locator('.itc-popout')).toBeInViewport();
await setTimeBounds(page, start, end);
await setTimeBounds(page, startDate, endDate);
await page.keyboard.press('Enter');
}
@ -645,7 +642,8 @@ async function renameObjectFromContextMenu(page, url, newName) {
await page.click('[aria-label="Save"]');
}
export {
// eslint-disable-next-line no-undef
module.exports = {
createDomainObjectWithDefaults,
createExampleTelemetryObject,
createNotification,
@ -653,17 +651,16 @@ export {
expandEntireTree,
expandTreePaneItemByName,
getCanvasPixels,
getFocusedObjectUuid,
getHashUrlToDomainObject,
getFocusedObjectUuid,
navigateToObjectWithFixedTimeBounds,
openObjectTreeContextMenu,
renameObjectFromContextMenu,
setEndOffset,
setFixedTimeMode,
setIndependentTimeConductorBounds,
setRealTimeMode,
setStartOffset,
setEndOffset,
setTimeConductorBounds,
setTimeConductorMode,
waitForPlotsToRender
setIndependentTimeConductorBounds,
waitForPlotsToRender,
renameObjectFromContextMenu
};

View File

@ -1,3 +1,4 @@
/* eslint-disable prettier/prettier */
/**
* Constants which may be used across all e2e tests.
*/
@ -7,30 +8,11 @@
* - 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)
// 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
* These constants are used for initial navigation in visual tests, in either fixed or realtime mode.
* They navigate to the 'My Items' folder at MISSION_TIME.
* 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
* - This is the URL that the browser will be directed to when running visual tests. This URL
* - hides the tree and inspector to prevent visual noise
* - sets the time bounds to a fixed range
*/
/** @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';
export const VISUAL_URL = './#/browse/mine?tc.mode=fixed&tc.startBound=1693592063607&tc.endBound=1693593893607&tc.timeSystem=utc&view=grid&hideInspector=true&hideTree=true';

View File

@ -1,5 +1,5 @@
/*****************************************************************************
* Open MCT, Copyright (c) 2014-2024, United States Government
* Open MCT, Copyright (c) 2014-2023, United States Government
* as represented by the Administrator of the National Aeronautics and Space
* Administration. All rights reserved.
*
@ -33,74 +33,34 @@
* existing ones.
*/
import AxeBuilder from '@axe-core/playwright';
import fs from 'fs';
import path from 'path';
import { fileURLToPath } from 'url';
const fs = require('fs');
const path = require('path');
const { test, expect } = require('../pluginFixtures');
const AxeBuilder = require('@axe-core/playwright').default;
import { expect, test } from './pluginFixtures.js';
// Constants for repeated values
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);
}
});
const TEST_RESULTS_DIR = './test-results';
/**
* 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.
*
* @typedef {object} GenerateReportOptions
* @property {string} [reportName] - The name for the report file.
*
* @param {import('playwright').Page} page - The page object from Playwright.
* @param {string} testCaseName - The name of the test case.
* @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.
* @param {GenerateReportOptions} [options={}] - The options for the report generation.
*
* @returns {Promise<object|null>} Returns the accessibility scan results if violations are found,
* otherwise returns null.
*/
export async function scanForA11yViolations(page, testCaseName, options = {}) {
/* eslint-disable no-undef */
exports.scanForA11yViolations = async function (page, testCaseName, options = {}) {
const builder = new AxeBuilder({ page });
builder.withTags(['wcag2aa']);
// https://github.com/dequelabs/axe-core/blob/develop/doc/rule-descriptions.md
builder.disableRules(['color-contrast']);
const accessibilityScanResults = await builder.analyze();
// Assert that no violations should be present
@ -131,6 +91,7 @@ export async function scanForA11yViolations(page, testCaseName, options = {}) {
console.log('No accessibility violations found, no report generated.');
return null;
}
}
};
export { expect, extendedTest as test };
exports.expect = expect;
exports.test = test;

View File

@ -1,5 +1,6 @@
/* eslint-disable no-undef */
/*****************************************************************************
* Open MCT, Copyright (c) 2014-2024, United States Government
* Open MCT, Copyright (c) 2014-2023, United States Government
* as represented by the Administrator of the National Aeronautics and Space
* Administration. All rights reserved.
*
@ -27,19 +28,19 @@
* GitHub issues.
*/
import { expect, request, test } from '@playwright/test';
import fs from 'fs';
import path from 'path';
import sinon from 'sinon';
import { fileURLToPath } from 'url';
import { v4 as uuid } from 'uuid';
const base = require('@playwright/test');
const { expect, request } = base;
const fs = require('fs');
const path = require('path');
const { v4: uuid } = require('uuid');
const sinon = require('sinon');
/**
* Takes a `ConsoleMessage` and returns a formatted string. Used to enable console log error detection.
* @see {@link https://github.com/microsoft/playwright/discussions/11690 Github Discussion}
* @private
* @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) {
const { url, lineNumber, columnNumber } = msg.location();
@ -60,16 +61,14 @@ function waitForAnimations(locator) {
);
}
const istanbulCLIOutput = fileURLToPath(new URL('.nyc_output', import.meta.url));
const extendedTest = test.extend({
/**
* Path to output raw coverage files. Can be overridden in Playwright config file.
* 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');
coveragePath: [istanbulCLIOutput, { option: true }],
exports.test = base.test.extend({
/**
* 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.
@ -98,7 +97,7 @@ const extendedTest = test.extend({
async ({ context, clockOptions }, use) => {
if (clockOptions !== undefined) {
await context.addInitScript({
path: fileURLToPath(new URL('../node_modules/sinon/pkg/sinon.js', import.meta.url))
path: path.join(__dirname, '../', './node_modules/sinon/pkg/sinon.js')
});
await context.addInitScript((options) => {
window.__clock = sinon.useFakeTimers(options);
@ -112,55 +111,21 @@ const extendedTest = test.extend({
scope: 'test'
}
],
/**
* Exposes a function to manually tick the clock. This is useful when overriding the clock to not
* tick (`shouldAdvanceTime: false`) for visual tests, as events such as re-renders and router params
* updates are clock-driven and must be manually ticked.
*
* Usage:
* ```js
* test.describe('Manual Clock Tick', () => {
* test.use({
* clockOptions: {
* now: MISSION_TIME, // Set to the desired time
* shouldAdvanceTime: false // Clock overridden to no longer tick
* }
* });
* test('Visual - Manual Clock Tick', async ({ page, tick }) => {
* // Tick the clock 2 seconds in the future
* await tick(2000);
* });
* });
* ```
*
* @param {Object} param0
* @param {import('@playwright/test').Page} param0.page
* @param {import('@playwright/test').Use} param0.use
*/
tick: async ({ page }, use) => {
// eslint-disable-next-line func-style
const tick = async (milliseconds) => {
await page.evaluate((_milliseconds) => {
window.__clock.tick(_milliseconds);
}, milliseconds);
};
await use(tick);
},
/**
* Extends the base context class to add codecoverage shim.
* @see {@link https://github.com/mxschmitt/playwright-test-coverage Github Project}
*/
context: async ({ context, coveragePath }, use) => {
context: async ({ context }, use) => {
await context.addInitScript(() =>
window.addEventListener('beforeunload', () =>
window.collectIstanbulCoverage(JSON.stringify(window.__coverage__))
)
);
await fs.promises.mkdir(coveragePath, { recursive: true });
await fs.promises.mkdir(istanbulCLIOutput, { recursive: true });
await context.exposeFunction('collectIstanbulCoverage', (coverageJSON) => {
if (coverageJSON) {
fs.writeFileSync(
path.join(coveragePath, `playwright_coverage_${uuid()}.json`),
path.join(istanbulCLIOutput, `playwright_coverage_${uuid()}.json`),
coverageJSON
);
}
@ -168,9 +133,9 @@ const extendedTest = test.extend({
await use(context);
for (const page of context.pages()) {
await page.evaluate(() => {
window.collectIstanbulCoverage(JSON.stringify(window.__coverage__));
});
await page.evaluate(() =>
window.collectIstanbulCoverage(JSON.stringify(window.__coverage__))
);
}
},
/**
@ -189,14 +154,18 @@ const extendedTest = test.extend({
// function in the generatorWorker context. This is necessary
// to ensure that example telemetry data is generated for the new clock time.
if (clockOptions?.now !== undefined) {
page.on('worker', (worker) => {
page.on(
'worker',
(worker) => {
if (worker.url().includes('generatorWorker')) {
worker.evaluate((time) => {
self.Date.now = () => time;
}, clockOptions.now);
}
});
}
},
clockOptions.now
);
}
// Capture any console errors during test execution
const messages = [];
@ -232,4 +201,6 @@ const extendedTest = test.extend({
}
});
export { expect, request, extendedTest as test, waitForAnimations };
exports.expect = expect;
exports.request = request;
exports.waitForAnimations = waitForAnimations;

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,5 +1,5 @@
/*****************************************************************************
* Open MCT, Copyright (c) 2014-2024, United States Government
* Open MCT, Copyright (c) 2014-2023, United States Government
* as represented by the Administrator of the National Aeronautics and Space
* Administration. All rights reserved.
*
@ -19,16 +19,24 @@
* this source code distribution or the Licensing information page available
* at runtime from the About dialog for additional information.
*****************************************************************************/
import { fileURLToPath } from 'url';
import { expect } from '../pluginFixtures.js';
/* global __dirname */
const path = require('path');
/**
* @param {import('@playwright/test').Page} page
*/
export async function navigateToFaultManagementWithExample(page) {
async function navigateToFaultManagementWithExample(page) {
await page.addInitScript({ path: path.join(__dirname, './', 'addInitExampleFaultProvider.js') });
await navigateToFaultItemInTree(page);
}
/**
* @param {import('@playwright/test').Page} page
*/
async function navigateToFaultManagementWithStaticExample(page) {
await page.addInitScript({
path: fileURLToPath(new URL('./addInitExampleFaultProvider.js', import.meta.url))
path: path.join(__dirname, './', 'addInitExampleFaultProviderStatic.js')
});
await navigateToFaultItemInTree(page);
@ -37,10 +45,8 @@ export async function navigateToFaultManagementWithExample(page) {
/**
* @param {import('@playwright/test').Page} page
*/
export async function navigateToFaultManagementWithStaticExample(page) {
await page.addInitScript({
path: fileURLToPath(new URL('./addInitExampleFaultProviderStatic.js', import.meta.url))
});
async function navigateToFaultManagementWithoutExample(page) {
await page.addInitScript({ path: path.join(__dirname, './', 'addInitFaultManagementPlugin.js') });
await navigateToFaultItemInTree(page);
}
@ -48,18 +54,7 @@ export async function navigateToFaultManagementWithStaticExample(page) {
/**
* @param {import('@playwright/test').Page} page
*/
export async function navigateToFaultManagementWithoutExample(page) {
await page.addInitScript({
path: fileURLToPath(new URL('./addInitFaultManagementPlugin.js', import.meta.url))
});
await navigateToFaultItemInTree(page);
}
/**
* @param {import('@playwright/test').Page} page
*/
export async function navigateToFaultItemInTree(page) {
async function navigateToFaultItemInTree(page) {
await page.goto('./', { waitUntil: 'networkidle' });
const faultManagementTreeItem = page
@ -77,95 +72,88 @@ export async function navigateToFaultItemInTree(page) {
/**
* @param {import('@playwright/test').Page} page
*/
export async function acknowledgeFault(page, rowNumber) {
async function acknowledgeFault(page, rowNumber) {
await openFaultRowMenu(page, rowNumber);
await page.getByLabel('Acknowledge', { exact: true }).click();
await page.getByLabel('Save').click();
await page.locator('.c-menu >> text="Acknowledge"').click();
// Click [aria-label="Save"]
await page.locator('[aria-label="Save"]').click();
}
/**
* @param {import('@playwright/test').Page} page
*/
export async function shelveMultipleFaults(page, ...nums) {
async function shelveMultipleFaults(page, ...nums) {
const selectRows = nums.map((num) => {
return selectFaultItem(page, num);
});
await Promise.all(selectRows);
await page.getByLabel('Shelve selected faults').click();
await page.getByLabel('Save').click();
await page.locator('button:has-text("Shelve")').click();
await page.locator('[aria-label="Save"]').click();
}
/**
* @param {import('@playwright/test').Page} page
*/
export async function acknowledgeMultipleFaults(page, ...nums) {
async function acknowledgeMultipleFaults(page, ...nums) {
const selectRows = nums.map((num) => {
return selectFaultItem(page, num);
});
await Promise.all(selectRows);
await page.locator('button:has-text("Acknowledge")').click();
await page.getByLabel('Save').click();
await page.locator('[aria-label="Save"]').click();
}
/**
* @param {import('@playwright/test').Page} page
*/
export async function shelveFault(page, rowNumber) {
async function shelveFault(page, rowNumber) {
await openFaultRowMenu(page, rowNumber);
await page.locator('.c-menu >> text="Shelve"').click();
// Click [aria-label="Save"]
await page.getByLabel('Save').click();
await page.locator('[aria-label="Save"]').click();
}
/**
* @param {import('@playwright/test').Page} page
*/
export async function changeViewTo(page, view) {
async function changeViewTo(page, view) {
await page.locator('.c-fault-mgmt__search-row select').first().selectOption(view);
}
/**
* @param {import('@playwright/test').Page} page
*/
export async function sortFaultsBy(page, sort) {
async function sortFaultsBy(page, sort) {
await page.locator('.c-fault-mgmt__list-header-sortButton select').selectOption(sort);
}
/**
* @param {import('@playwright/test').Page} page
*/
export async function enterSearchTerm(page, term) {
async function enterSearchTerm(page, term) {
await page.locator('.c-fault-mgmt-search [aria-label="Search Input"]').fill(term);
}
/**
* @param {import('@playwright/test').Page} page
*/
export async function clearSearch(page) {
async function clearSearch(page) {
await enterSearchTerm(page, '');
}
/**
* @param {import('@playwright/test').Page} page
*/
export async function selectFaultItem(page, rowNumber) {
await page
.getByLabel('Select fault')
.nth(rowNumber - 1)
.check({
// Need force here because checkbox state is changed by an event emitted by the checkbox
// eslint-disable-next-line playwright/no-force-option
force: true
});
await expect(page.getByLabel('Select fault').nth(rowNumber - 1)).toBeChecked();
async function selectFaultItem(page, rowNumber) {
await page.locator(`.c-fault-mgmt-item > input >> nth=${rowNumber - 1}`).check();
}
/**
* @param {import('@playwright/test').Page} page
*/
export async function getHighestSeverity(page) {
async function getHighestSeverity(page) {
const criticalCount = await page.locator('[title=CRITICAL]').count();
const warningCount = await page.locator('[title=WARNING]').count();
@ -181,7 +169,7 @@ export async function getHighestSeverity(page) {
/**
* @param {import('@playwright/test').Page} page
*/
export async function getLowestSeverity(page) {
async function getLowestSeverity(page) {
const warningCount = await page.locator('[title=WARNING]').count();
const watchCount = await page.locator('[title=WATCH]').count();
@ -197,7 +185,7 @@ export async function getLowestSeverity(page) {
/**
* @param {import('@playwright/test').Page} page
*/
export async function getFaultResultCount(page) {
async function getFaultResultCount(page) {
const count = await page.locator('.c-faults-list-view-item-body > .c-fault-mgmt__list').count();
return count;
@ -206,7 +194,7 @@ export async function getFaultResultCount(page) {
/**
* @param {import('@playwright/test').Page} page
*/
export function getFault(page, rowNumber) {
function getFault(page, rowNumber) {
const fault = page.locator(
`.c-faults-list-view-item-body > .c-fault-mgmt__list >> nth=${rowNumber - 1}`
);
@ -217,7 +205,7 @@ export function getFault(page, rowNumber) {
/**
* @param {import('@playwright/test').Page} page
*/
export function getFaultByName(page, name) {
function getFaultByName(page, name) {
const fault = page.locator(`.c-fault-mgmt__list-faultname:has-text("${name}")`);
return fault;
@ -226,7 +214,7 @@ export function getFaultByName(page, name) {
/**
* @param {import('@playwright/test').Page} page
*/
export async function getFaultName(page, rowNumber) {
async function getFaultName(page, rowNumber) {
const faultName = await page
.locator(`.c-fault-mgmt__list-faultname >> nth=${rowNumber - 1}`)
.textContent();
@ -237,7 +225,7 @@ export async function getFaultName(page, rowNumber) {
/**
* @param {import('@playwright/test').Page} page
*/
export async function getFaultSeverity(page, rowNumber) {
async function getFaultSeverity(page, rowNumber) {
const faultSeverity = await page
.locator(`.c-faults-list-view-item-body .c-fault-mgmt__list-severity >> nth=${rowNumber - 1}`)
.getAttribute('title');
@ -248,7 +236,7 @@ export async function getFaultSeverity(page, rowNumber) {
/**
* @param {import('@playwright/test').Page} page
*/
export async function getFaultNamespace(page, rowNumber) {
async function getFaultNamespace(page, rowNumber) {
const faultNamespace = await page
.locator(`.c-fault-mgmt__list-path >> nth=${rowNumber - 1}`)
.textContent();
@ -259,7 +247,7 @@ export async function getFaultNamespace(page, rowNumber) {
/**
* @param {import('@playwright/test').Page} page
*/
export async function getFaultTriggerTime(page, rowNumber) {
async function getFaultTriggerTime(page, rowNumber) {
const faultTriggerTime = await page
.locator(`.c-fault-mgmt__list-trigTime >> nth=${rowNumber - 1} >> .c-fault-mgmt-item__value`)
.textContent();
@ -270,10 +258,36 @@ export async function getFaultTriggerTime(page, rowNumber) {
/**
* @param {import('@playwright/test').Page} page
*/
export async function openFaultRowMenu(page, rowNumber) {
async function openFaultRowMenu(page, rowNumber) {
// select
await page
.getByLabel('Disposition actions')
.nth(rowNumber - 1)
.locator(`.c-fault-mgmt-item > .c-fault-mgmt__list-action-button >> nth=${rowNumber - 1}`)
.click();
}
// eslint-disable-next-line no-undef
module.exports = {
navigateToFaultManagementWithExample,
navigateToFaultManagementWithStaticExample,
navigateToFaultManagementWithoutExample,
navigateToFaultItemInTree,
acknowledgeFault,
shelveMultipleFaults,
acknowledgeMultipleFaults,
shelveFault,
changeViewTo,
sortFaultsBy,
enterSearchTerm,
clearSearch,
selectFaultItem,
getHighestSeverity,
getLowestSeverity,
getFaultResultCount,
getFault,
getFaultByName,
getFaultName,
getFaultSeverity,
getFaultNamespace,
getFaultTriggerTime,
openFaultRowMenu
};

View File

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

View File

@ -1,5 +1,5 @@
/*****************************************************************************
* Open MCT, Copyright (c) 2014-2024, United States Government
* Open MCT, Copyright (c) 2014-2023, United States Government
* as represented by the Administrator of the National Aeronautics and Space
* Administration. All rights reserved.
*
@ -20,8 +20,7 @@
* at runtime from the About dialog for additional information.
*****************************************************************************/
import { createDomainObjectWithDefaults, createPlanFromJSON } from '../appActions.js';
import { expect } from '../pluginFixtures.js';
import { expect } from '../pluginFixtures';
/**
* Asserts that the number of activities in the plan view matches the number of
@ -29,7 +28,7 @@ import { expect } from '../pluginFixtures.js';
* 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.
* @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)
*/
export async function assertPlanActivities(page, plan, objectUrl) {
@ -86,7 +85,7 @@ function activitiesWithinTimeBounds(start1, end1, start2, end2) {
* Asserts that the swim lanes / groups in the plan view matches the order of
* groups in the plan data.
* @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)
*/
export async function assertPlanOrderedSwimLanes(page, plan, objectUrl) {
@ -110,51 +109,21 @@ export async function assertPlanOrderedSwimLanes(page, plan, objectUrl) {
* Navigate to the plan view, switch to fixed time mode,
* and set the bounds to span all activities.
* @param {import('@playwright/test').Page} page
* @param {Object} planJson
* @param {object} planJson
* @param {string} planObjectUrl
*/
export async function setBoundsToSpanAllActivities(page, planJson, planObjectUrl) {
const activities = Object.values(planJson).flat();
// Get the earliest start value
const start = getEarliestStartTime(planJson);
const start = Math.min(...activities.map((activity) => activity.start));
// Get the latest end value
const end = getLatestEndTime(planJson);
const end = Math.max(...activities.map((activity) => activity.end));
// Set the start and end bounds to the earliest start and latest end
await page.goto(
`${planObjectUrl}?tc.mode=fixed&tc.startBound=${start}&tc.endBound=${end}&tc.timeSystem=utc&view=plan.view`
);
}
/**
* @param {Object} planJson
* @returns {number}
*/
export function getEarliestStartTime(planJson) {
const activities = Object.values(planJson).flat();
return Math.min(...activities.map((activity) => activity.start));
}
/**
*
* @param {Object} planJson
* @returns {number}
*/
export function getLatestEndTime(planJson) {
const activities = Object.values(planJson).flat();
return Math.max(...activities.map((activity) => activity.end));
}
/**
*
* @param {object} planJson
* @returns {object}
*/
export function getFirstActivity(planJson) {
const groups = Object.keys(planJson);
const firstGroupKey = groups[0];
const firstGroupItems = planJson[firstGroupKey];
return firstGroupItems[0];
}
/**
* Uses the Open MCT API to set the status of a plan to 'draft'.
* @param {import('@playwright/test').Page} page
@ -185,55 +154,3 @@ export async function addPlanGetInterceptor(page) {
});
});
}
/**
* Create a Plan from JSON and add it to a Timelist and Navigate to the Plan view
* @param {import('@playwright/test').Page} page
*/
export async function createTimelistWithPlanAndSetActivityInProgress(page, planJson) {
await page.goto('./', { waitUntil: 'domcontentloaded' });
const timelist = await createDomainObjectWithDefaults(page, {
name: 'Time List',
type: 'Time List'
});
await createPlanFromJSON(page, {
name: 'Test Plan',
json: planJson,
parent: timelist.uuid
});
// Ensure that all activities are shown in the expanded view
const groups = Object.keys(planJson);
const firstGroupKey = groups[0];
const firstGroupItems = planJson[firstGroupKey];
const firstActivityForPlan = firstGroupItems[0];
const lastActivity = firstGroupItems[firstGroupItems.length - 1];
const startBound = firstActivityForPlan.start;
const endBound = lastActivity.end;
// Switch to fixed time mode with all plan events within the bounds
await page.goto(
`${timelist.url}?tc.mode=fixed&tc.startBound=${startBound}&tc.endBound=${endBound}&tc.timeSystem=utc&view=timelist.view`
);
// Change the object to edit mode
await page.getByRole('button', { name: 'Edit Object' }).click();
// Find the display properties section in the inspector
await page.getByRole('tab', { name: 'View Properties' }).click();
// Switch to expanded view and save the setting
await page.getByLabel('Display Style').selectOption({ label: 'Expanded' });
// Click on the "Save" button
await page.getByRole('button', { name: 'Save' }).click();
await page.getByRole('listitem', { name: 'Save and Finish Editing' }).click();
const anActivity = page.getByRole('row').nth(0);
// Set the activity to in progress
await anActivity.click();
await page.getByRole('tab', { name: 'Activity' }).click();
await page.getByLabel('Activity Status', { exact: true }).selectOption({ label: 'In progress' });
}

View File

@ -1,5 +1,5 @@
/*****************************************************************************
* Open MCT, Copyright (c) 2014-2024, United States Government
* Open MCT, Copyright (c) 2014-2023, United States Government
* as represented by the Administrator of the National Aeronautics and Space
* Administration. All rights reserved.
*
@ -20,15 +20,15 @@
* at runtime from the About dialog for additional information.
*****************************************************************************/
import { waitForPlotsToRender } from '../appActions.js';
import { expect } from '../pluginFixtures.js';
import { expect } from '../pluginFixtures';
const { waitForPlotsToRender } = require('../appActions');
/**
* Given a canvas and a set of points, tags the points on the canvas.
* @param {import('@playwright/test').Page} page
* @param {HTMLCanvasElement} canvas 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} xEnd a telemetry item with a plot
* @param {Number} yEnd a telemetry item with a plot
* @returns {Promise}
*/
export async function createTags({ page, canvas, xEnd = 700, yEnd = 520 }) {

View File

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

View File

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

View File

@ -1,8 +0,0 @@
// 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

File diff suppressed because it is too large Load Diff

View File

@ -1,27 +0,0 @@
{
"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.42.1",
"@axe-core/playwright": "4.8.5",
"sinon": "17.0.0"
},
"author": "NASA Ames Research Center",
"license": "Apache-2.0"
}

View File

@ -1,9 +1,9 @@
/* eslint-disable no-undef */
// playwright.config.js
// @ts-check
// eslint-disable-next-line no-unused-vars
import { devices } from '@playwright/test';
import { fileURLToPath } from 'url';
const { devices } = require('@playwright/test');
const MAX_FAILURES = 5;
const NUM_WORKERS = 2;
@ -11,12 +11,10 @@ const NUM_WORKERS = 2;
const config = {
retries: 2, //Retries 2 times for a total of 3 runs. When running sharded and with max-failures=5, this should ensure that flake is managed without failing the full suite
testDir: 'tests',
grepInvert: /@mobile/, //Ignore mobile tests
testIgnore: '**/*.perf.spec.js', //Ignore performance tests and define in playwright-perfromance.config.js
timeout: 60 * 1000,
webServer: {
command: 'npm run start:coverage',
cwd: fileURLToPath(new URL('../', import.meta.url)), // Provide cwd for the root of the project
url: 'http://localhost:8080/#',
timeout: 200 * 1000,
reuseExistingServer: true //This was originally disabled to prevent differences in local debugging vs. CI. However, it significantly speeds up local debugging.
@ -29,9 +27,7 @@ const config = {
ignoreHTTPSErrors: true,
screenshot: 'only-on-failure',
trace: 'on-first-retry',
video: 'off',
// @ts-ignore - custom configuration option for nyc codecoverage output path
coveragePath: fileURLToPath(new URL('../.nyc_output', import.meta.url))
video: 'off'
},
projects: [
{
@ -84,4 +80,4 @@ const config = {
]
};
export default config;
module.exports = config;

View File

@ -1,16 +1,18 @@
/* eslint-disable no-undef */
// playwright.config.js
// @ts-check
import { fileURLToPath } from 'url';
// eslint-disable-next-line no-unused-vars
const { devices } = require('@playwright/test');
/** @type {import('@playwright/test').PlaywrightTestConfig} */
const config = {
retries: 0,
testDir: 'tests',
testMatch: '**/*.e2e.spec.js', // only run e2e tests
testIgnore: '**/*.perf.spec.js',
timeout: 30 * 1000,
webServer: {
command: 'npm run start:coverage',
cwd: fileURLToPath(new URL('../', import.meta.url)), // Provide cwd for the root of the project
url: 'http://localhost:8080/#',
timeout: 120 * 1000,
reuseExistingServer: true
@ -34,6 +36,7 @@ const config = {
},
{
name: 'MMOC',
testMatch: '**/*.e2e.spec.js', // only run e2e tests
grepInvert: /@snapshot/,
use: {
browserName: 'chromium',
@ -45,6 +48,8 @@ const config = {
},
{
name: 'safari',
testMatch: '**/*.e2e.spec.js', // only run e2e tests
grep: /@ipad/, // only run ipad tests due to this bug https://github.com/microsoft/playwright/issues/8340
grepInvert: /@snapshot/,
use: {
browserName: 'webkit'
@ -52,6 +57,7 @@ const config = {
},
{
name: 'firefox',
testMatch: '**/*.e2e.spec.js', // only run e2e tests
grepInvert: /@snapshot/,
use: {
browserName: 'firefox'
@ -59,6 +65,7 @@ const config = {
},
{
name: 'canary',
testMatch: '**/*.e2e.spec.js', // only run e2e tests
grepInvert: /@snapshot/,
use: {
browserName: 'chromium',
@ -67,11 +74,22 @@ const config = {
},
{
name: 'chrome-beta',
testMatch: '**/*.e2e.spec.js', // only run e2e tests
grepInvert: /@snapshot/,
use: {
browserName: 'chromium',
channel: 'chrome-beta'
}
},
{
name: 'ipad',
testMatch: '**/*.e2e.spec.js', // only run e2e tests
grep: /@ipad/,
grepInvert: /@snapshot/,
use: {
browserName: 'webkit',
...devices['iPad (gen 7) landscape'] // Complete List https://github.com/microsoft/playwright/blob/main/packages/playwright-core/src/server/deviceDescriptorsSource.json
}
}
],
reporter: [
@ -86,4 +104,4 @@ const config = {
]
};
export default config;
module.exports = config;

View File

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

View File

@ -1,6 +1,7 @@
/* eslint-disable no-undef */
// playwright.config.js
// @ts-check
import { fileURLToPath } from 'url';
/** @type {import('@playwright/test').PlaywrightTestConfig} */
const config = {
retries: 1, //Only for debugging purposes for trace: 'on-first-retry'
@ -10,7 +11,6 @@ const config = {
workers: 1, //Only run in serial with 1 worker
webServer: {
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/#',
timeout: 200 * 1000,
reuseExistingServer: false
@ -40,4 +40,4 @@ const config = {
]
};
export default config;
module.exports = config;

View File

@ -1,6 +1,7 @@
/* eslint-disable no-undef */
// playwright.config.js
// @ts-check
import { fileURLToPath } from 'url';
/** @type {import('@playwright/test').PlaywrightTestConfig} */
const config = {
retries: 0, //Only for debugging purposes for trace: 'on-first-retry'
@ -10,7 +11,6 @@ const config = {
workers: 1, //Only run in serial with 1 worker
webServer: {
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/#',
timeout: 200 * 1000,
reuseExistingServer: false //Must be run with this option to prevent dev mode
@ -57,4 +57,4 @@ const config = {
]
};
export default config;
module.exports = config;

View File

@ -1,6 +1,7 @@
/* eslint-disable no-undef */
// playwright.config.js
// @ts-check
import { fileURLToPath } from 'url';
/** @type {import('@playwright/test').PlaywrightTestConfig<{ theme: string }>} */
const config = {
retries: 0, // Visual tests should never retry due to snapshot comparison errors. Leaving as a shim
@ -10,7 +11,6 @@ const config = {
workers: 1, //Lower stress on Circle CI Agent for Visual tests https://github.com/percy/cli/discussions/1067
webServer: {
command: 'npm run start:coverage',
cwd: fileURLToPath(new URL('../', import.meta.url)), // Provide cwd for the root of the project
url: 'http://localhost:8080/#',
timeout: 200 * 1000,
reuseExistingServer: !process.env.CI
@ -51,4 +51,4 @@ const config = {
]
};
export default config;
module.exports = config;

View File

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

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

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

View File

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

File diff suppressed because one or more lines are too long

View File

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

View File

@ -6,7 +6,7 @@
"localStorage": [
{
"name": "mct",
"value": "{\"mine\":{\"identifier\":{\"key\":\"mine\",\"namespace\":\"\"},\"name\":\"My Items\",\"type\":\"folder\",\"composition\":[{\"key\":\"67ca2e0a-b37e-4eda-86a4-ccdbb228bbc0\",\"namespace\":\"\"}],\"location\":\"ROOT\",\"modified\":1732413601720,\"created\":1732413600920,\"persisted\":1732413601720},\"67ca2e0a-b37e-4eda-86a4-ccdbb228bbc0\":{\"identifier\":{\"key\":\"67ca2e0a-b37e-4eda-86a4-ccdbb228bbc0\",\"namespace\":\"\"},\"name\":\"Overlay Plot with 5s Delay\",\"type\":\"telemetry.plot.overlay\",\"composition\":[{\"key\":\"8f524b49-ad06-47f9-98e0-087b31a2f3e0\",\"namespace\":\"\"}],\"configuration\":{\"series\":[{\"identifier\":{\"key\":\"8f524b49-ad06-47f9-98e0-087b31a2f3e0\",\"namespace\":\"\"}}]},\"notes\":\"framework/generateLocalStorageData.e2e.spec.js\\nGenerate Visual Test Data @localStorage @generatedata\\nGenerate Overlay Plot with 5s Delay\\nchrome\",\"modified\":1732413603020,\"location\":\"mine\",\"created\":1732413601720,\"persisted\":1732413603020},\"8f524b49-ad06-47f9-98e0-087b31a2f3e0\":{\"identifier\":{\"key\":\"8f524b49-ad06-47f9-98e0-087b31a2f3e0\",\"namespace\":\"\"},\"name\":\"VIPER Rover Heading\",\"type\":\"generator\",\"telemetry\":{\"period\":10,\"amplitude\":1,\"offset\":0,\"dataRateInHz\":1,\"phase\":0,\"randomness\":0,\"loadDelay\":5000,\"infinityValues\":false,\"exceedFloat32\":false,\"staleness\":false},\"modified\":1732413602920,\"location\":\"67ca2e0a-b37e-4eda-86a4-ccdbb228bbc0\",\"created\":1732413602420,\"persisted\":1732413602920}}"
"value": "{\"mine\":{\"identifier\":{\"key\":\"mine\",\"namespace\":\"\"},\"name\":\"My Items\",\"type\":\"folder\",\"composition\":[{\"key\":\"98161570-a735-4a50-9c75-11b346ad3789\",\"namespace\":\"\"}],\"location\":\"ROOT\",\"modified\":1732413601340,\"created\":1732413600580,\"persisted\":1732413601340},\"98161570-a735-4a50-9c75-11b346ad3789\":{\"identifier\":{\"key\":\"98161570-a735-4a50-9c75-11b346ad3789\",\"namespace\":\"\"},\"name\":\"Overlay Plot with 5s Delay\",\"type\":\"telemetry.plot.overlay\",\"composition\":[{\"key\":\"477e60bb-4cba-4603-b4c9-2281ccf7e054\",\"namespace\":\"\"}],\"configuration\":{\"series\":[{\"identifier\":{\"key\":\"477e60bb-4cba-4603-b4c9-2281ccf7e054\",\"namespace\":\"\"}}]},\"notes\":\"framework/generateLocalStorageData.e2e.spec.js\\nGenerate Visual Test Data @localStorage @generatedata\\nGenerate Overlay Plot with 5s Delay\\nchrome\",\"modified\":1732413602660,\"location\":\"mine\",\"created\":1732413601340,\"persisted\":1732413602660},\"477e60bb-4cba-4603-b4c9-2281ccf7e054\":{\"identifier\":{\"key\":\"477e60bb-4cba-4603-b4c9-2281ccf7e054\",\"namespace\":\"\"},\"name\":\"VIPER Rover Heading\",\"type\":\"generator\",\"telemetry\":{\"period\":10,\"amplitude\":1,\"offset\":0,\"dataRateInHz\":1,\"phase\":0,\"randomness\":0,\"loadDelay\":5000,\"infinityValues\":false,\"staleness\":false},\"modified\":1732413602520,\"location\":\"98161570-a735-4a50-9c75-11b346ad3789\",\"created\":1732413602040,\"persisted\":1732413602520}}"
},
{
"name": "mct-tree-expanded",

File diff suppressed because one or more lines are too long

View File

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

View File

@ -1,5 +1,5 @@
/*****************************************************************************
* Open MCT, Copyright (c) 2014-2024, United States Government
* Open MCT, Copyright (c) 2014-2023, United States Government
* as represented by the Administrator of the National Aeronautics and Space
* Administration. All rights reserved.
*
@ -26,12 +26,11 @@ relates to how we've extended it (i.e. ./e2e/baseFixtures.js) and assumptions ma
(`npm start` and ./e2e/webpack-dev-middleware.js)
*/
import { expect, test } from '../../baseFixtures.js';
import { MISSION_TIME } from '../../constants.js';
const { test } = require('../../baseFixtures.js');
test.describe('baseFixtures tests', () => {
//Skip this test for now https://github.com/nasa/openmct/issues/6785
test('Verify that tests fail if console.error is thrown', async ({ page }) => {
test.fixme('Verify that tests fail if console.error is thrown', async ({ page }) => {
test.fail();
//Go to baseURL
await page.goto('./', { waitUntil: 'domcontentloaded' });
@ -53,27 +52,3 @@ test.describe('baseFixtures tests', () => {
]);
});
});
test.describe('baseFixtures tests @clock', () => {
test.use({
clockOptions: {
now: MISSION_TIME,
shouldAdvanceTime: false
}
});
test.beforeEach(async ({ page }) => {
await page.goto('./', { waitUntil: 'domcontentloaded' });
});
test('Can use clockOptions and tick fixtures to control the clock', async ({ page, tick }) => {
let time = await page.evaluate(() => new Date().getTime());
expect(time).toBe(MISSION_TIME);
await tick(1000);
time = await page.evaluate(() => new Date().getTime());
expect(time).toBe(MISSION_TIME + 1000 * 1);
await tick(1000);
time = await page.evaluate(() => new Date().getTime());
expect(time).toBe(MISSION_TIME + 1000 * 2);
});
});

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,5 +1,5 @@
/*****************************************************************************
* Open MCT, Copyright (c) 2014-2024, United States Government
* Open MCT, Copyright (c) 2014-2023, United States Government
* as represented by the Administrator of the National Aeronautics and Space
* Administration. All rights reserved.
*
@ -19,29 +19,30 @@
* this source code distribution or the Licensing information page available
* at runtime from the About dialog for additional information.
*****************************************************************************/
/* global __dirname */
/*
This test suite is dedicated to tests which verify the basic operations surrounding conditionSets. Note: this
suite is sharing state between tests which is considered an anti-pattern. Implementing in this way to
demonstrate some playwright for test developers. This pattern should not be re-used in other CRUD suites.
*/
import { fileURLToPath } from 'url';
import {
const { test, expect } = require('../../../../pluginFixtures.js');
const {
createDomainObjectWithDefaults,
createExampleTelemetryObject
} from '../../../../appActions.js';
import { expect, test } from '../../../../pluginFixtures.js';
} = require('../../../../appActions');
const path = require('path');
let conditionSetUrl;
let getConditionSetIdentifierFromUrl;
test.describe.serial('Condition Set CRUD Operations on @localStorage @2p', () => {
test.describe.serial('Condition Set CRUD Operations on @localStorage', () => {
test.beforeAll(async ({ browser }) => {
//TODO: This needs to be refactored
const context = await browser.newContext();
const page = await context.newPage();
await page.goto('./', { waitUntil: 'domcontentloaded' });
await page.getByRole('button', { name: 'Create' }).click();
await page.click('button:has-text("Create")');
await page.locator('li[role="menuitem"]:has-text("Condition Set")').click();
@ -49,32 +50,26 @@ test.describe.serial('Condition Set CRUD Operations on @localStorage @2p', () =>
//Save localStorage for future test execution
await context.storageState({
path: fileURLToPath(
new URL('../../../../test-data/recycled_local_storage.json', import.meta.url)
)
path: path.resolve(__dirname, '../../../../test-data/recycled_local_storage.json')
});
//Set object identifier from url
conditionSetUrl = page.url();
getConditionSetIdentifierFromUrl = conditionSetUrl.split('/').pop().split('?')[0];
console.debug(`getConditionSetIdentifierFromUrl: ${getConditionSetIdentifierFromUrl}`);
await page.close();
});
//Load localStorage for subsequent tests
test.use({
storageState: fileURLToPath(
new URL('../../../../test-data/recycled_local_storage.json', import.meta.url)
)
storageState: path.resolve(__dirname, '../../../../test-data/recycled_local_storage.json')
});
//Begin suite of tests again localStorage
test.fixme(
'Condition set object properties persist in main view and inspector @localStorage',
async ({ page }) => {
test.info().annotations.push({
type: 'issue',
description: 'https://github.com/nasa/openmct/issues/7421'
});
test('Condition set object properties persist in main view and inspector @localStorage', async ({
page
}) => {
//Navigate to baseURL with injected localStorage
await page.goto(conditionSetUrl, { waitUntil: 'networkidle' });
@ -95,8 +90,7 @@ test.describe.serial('Condition Set CRUD Operations on @localStorage @2p', () =>
.toContainText('Unnamed Condition Set');
//Assertions on loaded Condition Set in Inspector
expect.soft(page.locator('_vue=item.name=Unnamed Condition Set')).toBeTruthy();
}
);
});
test('condition set object can be modified on @localStorage', async ({ page, openmctConfig }) => {
const { myItemsFolderName } = openmctConfig;
@ -198,7 +192,7 @@ test.describe.serial('Condition Set CRUD Operations on @localStorage @2p', () =>
.first()
.click();
// Click hamburger button
await page.locator('[title="More actions"]').click();
await page.locator('[title="More options"]').click();
// Click 'Remove' and press OK
await page.locator('li[role="menuitem"]:has-text("Remove")').click();
@ -236,7 +230,7 @@ test.describe('Basic Condition Set Use', () => {
await page.goto(conditionSet.url);
// Change the object to edit mode
await page.getByLabel('Edit Object').click();
await page.locator('[title="Edit"]').click();
// Click Add Condition button
await page.locator('#addCondition').click();
@ -264,7 +258,7 @@ test.describe('Basic Condition Set Use', () => {
await page.goto(conditionSet.url);
// Change the object to edit mode
await page.getByLabel('Edit Object').click();
await page.locator('[title="Edit"]').click();
// Expand the 'My Items' folder in the left tree
page.click('button[title="Show selected item in tree"]');
@ -286,32 +280,22 @@ test.describe('Basic Condition Set Use', () => {
await page.locator('button[title="Save"]').click();
await page.getByRole('listitem', { name: 'Save and Finish Editing' }).click();
await page.getByLabel('Open the View Switcher Menu').click();
await page.click('button[title="Change the current view"]');
await expect(page.getByRole('menuitem', { name: /Lad Table/ })).toBeHidden();
await expect(page.getByRole('menuitem', { name: /Conditions View/ })).toBeVisible();
await expect(page.getByRole('menuitem', { name: /Plot/ })).toBeVisible();
await expect(page.getByRole('menuitem', { name: /Telemetry Table/ })).toBeVisible();
await page.getByLabel('Plot').click();
await expect(
page.getByLabel('Plot Legend Collapsed').getByText('Test Condition Set')
).toBeVisible();
await page.getByLabel('Open the View Switcher Menu').click();
await page.getByLabel('Telemetry Table').click();
await expect(page.getByRole('searchbox', { name: 'output filter input' })).toBeVisible();
await page.getByLabel('Open the View Switcher Menu').click();
await page.getByLabel('Conditions View').click();
await expect(page.getByText('Current Output')).toBeVisible();
});
test('ConditionSet has correct outputs when telemetry is and is not available', async ({
page
}) => {
const exampleTelemetry = await createExampleTelemetryObject(page);
await page.getByLabel('Show selected item in tree').click();
await page.getByTitle('Show selected item in tree').click();
await page.goto(conditionSet.url);
// Change the object to edit mode
await page.getByLabel('Edit Object').click();
await page.locator('[title="Edit"]').click();
// Create two conditions
await page.locator('#addCondition').click();
@ -378,7 +362,7 @@ test.describe('Basic Condition Set Use', () => {
// Edit SWG to add 8 second loading delay to simulate the case
// where telemetry is not available.
await page.getByTitle('More actions').click();
await page.getByTitle('More options').click();
await page.getByRole('menuitem', { name: 'Edit Properties...' }).click();
await page.getByRole('spinbutton', { name: 'Loading Delay (ms)' }).fill('8000');
await page.getByLabel('Save').click();
@ -388,90 +372,4 @@ test.describe('Basic Condition Set Use', () => {
await page.goto(conditionSet.url);
await expect(outputValue).toHaveText('---');
});
test('ConditionSet has correct outputs when test data is enabled', async ({ page }) => {
const exampleTelemetry = await createExampleTelemetryObject(page);
await page.getByLabel('Show selected item in tree').click();
await page.goto(conditionSet.url);
// Change the object to edit mode
await page.getByLabel('Edit Object').click();
// Create two conditions
await page.locator('#addCondition').click();
await page.locator('#addCondition').click();
await page.locator('#conditionCollection').getByRole('textbox').nth(0).fill('First Condition');
await page.locator('#conditionCollection').getByRole('textbox').nth(1).fill('Second Condition');
// Add Telemetry to ConditionSet
const sineWaveGeneratorTreeItem = page
.getByRole('tree', {
name: 'Main Tree'
})
.getByRole('treeitem', {
name: exampleTelemetry.name
});
const conditionCollection = page.locator('#conditionCollection');
await sineWaveGeneratorTreeItem.dragTo(conditionCollection);
// Modify First Criterion
const firstCriterionTelemetry = page.locator(
'[aria-label="Criterion Telemetry Selection"] >> nth=0'
);
firstCriterionTelemetry.selectOption({ label: exampleTelemetry.name });
const firstCriterionMetadata = page.locator(
'[aria-label="Criterion Metadata Selection"] >> nth=0'
);
firstCriterionMetadata.selectOption({ label: 'Sine' });
const firstCriterionComparison = page.locator(
'[aria-label="Criterion Comparison Selection"] >> nth=0'
);
firstCriterionComparison.selectOption({ label: 'is greater than or equal to' });
const firstCriterionInput = page.locator('[aria-label="Criterion Input"] >> nth=0');
await firstCriterionInput.fill('0');
// Modify Second Criterion
const secondCriterionTelemetry = page.locator(
'[aria-label="Criterion Telemetry Selection"] >> nth=1'
);
await secondCriterionTelemetry.selectOption({ label: exampleTelemetry.name });
const secondCriterionMetadata = page.locator(
'[aria-label="Criterion Metadata Selection"] >> nth=1'
);
await secondCriterionMetadata.selectOption({ label: 'Sine' });
const secondCriterionComparison = page.locator(
'[aria-label="Criterion Comparison Selection"] >> nth=1'
);
await secondCriterionComparison.selectOption({ label: 'is less than' });
const secondCriterionInput = page.locator('[aria-label="Criterion Input"] >> nth=1');
await secondCriterionInput.fill('0');
// Enable test data
await page.getByLabel('Apply Test Data').nth(1).click();
const testDataTelemetry = page.locator('[aria-label="Test Data Telemetry Selection"] >> nth=0');
await testDataTelemetry.selectOption({ label: exampleTelemetry.name });
const testDataMetadata = page.locator('[aria-label="Test Data Metadata Selection"] >> nth=0');
await testDataMetadata.selectOption({ label: 'Sine' });
const testInput = page.locator('[aria-label="Test Data Input"] >> nth=0');
await testInput.fill('0');
// Validate that the condition set is evaluating and outputting
// the correct value when the underlying telemetry subscription is active.
let outputValue = page.locator('[aria-label="Current Output Value"]');
await expect(outputValue).toHaveText('false');
await page.goto(exampleTelemetry.url);
});
test.fixme('Ensure condition sets work with telemetry like operator status', ({ page }) => {
test.info().annotations.push({
type: 'issue',
description: 'https://github.com/nasa/openmct/issues/7484'
});
});
});

View File

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

View File

@ -1,5 +1,5 @@
/*****************************************************************************
* Open MCT, Copyright (c) 2014-2024, United States Government
* Open MCT, Copyright (c) 2014-2023, United States Government
* as represented by the Administrator of the National Aeronautics and Space
* Administration. All rights reserved.
*
@ -19,132 +19,24 @@
* this source code distribution or the Licensing information page available
* at runtime from the About dialog for additional information.
*****************************************************************************/
import { fileURLToPath } from 'url';
import {
/* global __dirname */
const { test, expect } = require('../../../../pluginFixtures');
const path = require('path');
const {
createDomainObjectWithDefaults,
navigateToObjectWithFixedTimeBounds,
setStartOffset,
setFixedTimeMode,
setIndependentTimeConductorBounds,
setRealTimeMode,
setStartOffset
} from '../../../../appActions.js';
import { expect, test } from '../../../../pluginFixtures.js';
setIndependentTimeConductorBounds
} = require('../../../../appActions');
const CHILD_LAYOUT_STORAGE_STATE_PATH = fileURLToPath(
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 LOCALSTORAGE_PATH = path.resolve(
__dirname,
'../../../../test-data/display_layout_with_child_layouts.json'
);
const TINY_IMAGE_BASE64 =
'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', () => {
const PARENT_DISPLAY_LAYOUT_NAME = 'Parent Display Layout';
const CHILD_DISPLAY_LAYOUT_NAME1 = 'Child Layout 1';
@ -156,10 +48,10 @@ test.describe('Display Layout Toolbar Actions @localStorage', () => {
.filter({ hasText: 'Parent Display Layout Display Layout' })
.first()
.click();
await page.getByLabel('Edit Object').click();
await page.getByLabel('Edit').click();
});
test.use({
storageState: CHILD_LAYOUT_STORAGE_STATE_PATH
storageState: path.resolve(__dirname, LOCALSTORAGE_PATH)
});
test('can add/remove Text element to a single layout', async ({ page }) => {
@ -242,7 +134,7 @@ test.describe('Display Layout', () => {
name: 'Test Display Layout'
});
// Edit Display Layout
await page.getByLabel('Edit Object').click();
await page.locator('[title="Edit"]').click();
// Expand the 'My Items' folder in the left tree
await page.locator('.c-tree__item__view-control.c-disclosure-triangle').click();
@ -270,13 +162,6 @@ test.describe('Display Layout', () => {
const trimmedDisplayValue = displayLayoutValue.trim();
expect(trimmedDisplayValue).toBe(formattedTelemetryValue);
// ensure we can right click on the alpha-numeric widget and view historical data
await page.getByLabel(/Alpha-numeric telemetry value of.*/).click({
button: 'right'
});
await page.getByLabel('View Historical Data').click();
await expect(page.getByLabel('Plot Container Style Target')).toBeVisible();
});
test('alpha-numeric widget telemetry value exactly matches latest telemetry value received in fixed time', async ({
page
@ -287,7 +172,7 @@ test.describe('Display Layout', () => {
name: 'Test Display Layout'
});
// Edit Display Layout
await page.getByLabel('Edit Object').click();
await page.locator('[title="Edit"]').click();
// Expand the 'My Items' folder in the left tree
await page.locator('.c-tree__item__view-control.c-disclosure-triangle').click();
@ -329,7 +214,7 @@ test.describe('Display Layout', () => {
name: 'Test Display Layout'
});
// Edit Display Layout
await page.getByLabel('Edit Object').click();
await page.locator('[title="Edit"]').click();
// Expand the 'My Items' folder in the left tree
await page.locator('.c-tree__item__view-control.c-disclosure-triangle').click();
@ -371,7 +256,7 @@ test.describe('Display Layout', () => {
type: 'Display Layout'
});
// Edit Display Layout
await page.getByLabel('Edit Object').click();
await page.locator('[title="Edit"]').click();
// Expand the 'My Items' folder in the left tree
await page.locator('.c-tree__item__view-control.c-disclosure-triangle').click();
@ -417,7 +302,7 @@ test.describe('Display Layout', () => {
type: 'Display Layout'
});
// Edit Display Layout
await page.getByLabel('Edit Object').click();
await page.locator('[title="Edit"]').click();
// Expand the 'My Items' folder in the left tree
await page.locator('.c-tree__item__view-control.c-disclosure-triangle').click();
@ -445,7 +330,7 @@ test.describe('Display Layout', () => {
const startDate = '2021-12-30 01:01:00.000Z';
const endDate = '2021-12-30 01:11:00.000Z';
await setIndependentTimeConductorBounds(page, { start: startDate, end: endDate });
await setIndependentTimeConductorBounds(page, startDate, endDate);
// check image date
await expect(page.getByText('2021-12-30 01:11:00.000Z').first()).toBeVisible();
@ -473,7 +358,7 @@ test.describe('Display Layout', () => {
name: 'Test Display Layout'
});
// Edit Display Layout
await page.getByLabel('Edit Object').click();
await page.locator('[title="Edit"]').click();
// Expand the 'My Items' folder in the left tree
await page.locator('.c-tree__item__view-control.c-disclosure-triangle').click();
@ -565,7 +450,7 @@ async function removeLayoutObject(page, layoutObject) {
// eslint-disable-next-line playwright/no-force-option
.click({ force: true });
await page.getByTitle('Delete the selected object').click();
await page.getByRole('button', { name: 'OK', exact: true }).click();
await page.getByRole('button', { name: 'OK' }).click();
}
/**

View File

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

View File

@ -1,5 +1,5 @@
/*****************************************************************************
* Open MCT, Copyright (c) 2014-2024, United States Government
* Open MCT, Copyright (c) 2014-2023, United States Government
* as represented by the Administrator of the National Aeronautics and Space
* Administration. All rights reserved.
*
@ -19,17 +19,18 @@
* this source code distribution or the Licensing information page available
* at runtime from the About dialog for additional information.
*****************************************************************************/
/* global __dirname */
import { fileURLToPath } from 'url';
import {
const { test, expect } = require('../../../../pluginFixtures');
const {
createDomainObjectWithDefaults,
setIndependentTimeConductorBounds
} from '../../../../appActions.js';
import { expect, test } from '../../../../pluginFixtures.js';
} = require('../../../../appActions');
const path = require('path');
const LOCALSTORAGE_PATH = fileURLToPath(
new URL('../../../../test-data/flexible_layout_with_child_layouts.json', import.meta.url)
const LOCALSTORAGE_PATH = path.resolve(
__dirname,
'../../../../test-data/flexible_layout_with_child_layouts.json'
);
test.describe('Flexible Layout', () => {
@ -73,7 +74,7 @@ test.describe('Flexible Layout', () => {
}) => {
await page.goto(flexibleLayout.url);
// Edit Flexible Layout
await page.getByLabel('Edit Object').click();
await page.locator('[title="Edit"]').click();
// Expand the 'My Items' folder in the left tree
await page.locator('.c-tree__item__view-control.c-disclosure-triangle').first().click();
@ -166,7 +167,7 @@ test.describe('Flexible Layout', () => {
}) => {
await page.goto(flexibleLayout.url);
// Edit Flexible Layout
await page.getByLabel('Edit Object').click();
await page.locator('[title="Edit"]').click();
// Expand the 'My Items' folder in the left tree
await page.locator('.c-tree__item__view-control.c-disclosure-triangle').first().click();
@ -197,7 +198,7 @@ test.describe('Flexible Layout', () => {
});
await page.goto(flexibleLayout.url);
// Edit Flexible Layout
await page.getByLabel('Edit Object').click();
await page.locator('[title="Edit"]').click();
// Expand the 'My Items' folder in the left tree
await page.locator('.c-tree__item__view-control.c-disclosure-triangle').click();
@ -234,7 +235,7 @@ test.describe('Flexible Layout', () => {
await page.goto(flexibleLayout.url);
// Edit Flexible Layout
await page.getByLabel('Edit Object').click();
await page.locator('[title="Edit"]').click();
// Expand the 'My Items' folder in the left tree
await page.locator('.c-tree__item__view-control.c-disclosure-triangle').click();
@ -248,10 +249,11 @@ test.describe('Flexible Layout', () => {
await page.getByRole('listitem', { name: 'Save and Finish Editing' }).click();
// flip on independent time conductor
await setIndependentTimeConductorBounds(page, {
start: '2021-12-30 01:01:00.000Z',
end: '2021-12-30 01:11:00.000Z'
});
await setIndependentTimeConductorBounds(
page,
'2021-12-30 01:01:00.000Z',
'2021-12-30 01:11:00.000Z'
);
// check image date
await expect(page.getByText('2021-12-30 01:11:00.000Z').first()).toBeVisible();
@ -265,7 +267,7 @@ test.describe('Flexible Layout', () => {
test.describe('Flexible Layout Toolbar Actions @localStorage', () => {
test.use({
storageState: LOCALSTORAGE_PATH
storageState: path.resolve(__dirname, LOCALSTORAGE_PATH)
});
test.beforeEach(async ({ page }) => {
@ -275,43 +277,40 @@ test.describe('Flexible Layout Toolbar Actions @localStorage', () => {
.filter({ hasText: 'Parent Flexible Layout Flexible Layout' })
.first()
.click();
await page.getByLabel('Edit Object').click();
await page.getByLabel('Edit').click();
});
test('Add/Remove Container', async ({ page }) => {
test.info().annotations.push({
type: 'issue',
description: 'https://github.com/nasa/openmct/issues/7234'
});
const containerHandles = page.getByRole('columnheader', { name: 'Handle' });
expect(await containerHandles.count()).toEqual(2);
await page.getByRole('columnheader', { name: 'Container Handle 1' }).click();
expect(await page.getByRole('group', { name: 'Container' }).count()).toEqual(2);
await page.getByRole('group', { name: 'Container' }).nth(1).click();
await page.getByTitle('Add Container').click();
expect(await containerHandles.count()).toEqual(3);
expect(await page.getByRole('group', { name: 'Container' }).count()).toEqual(3);
await page.getByTitle('Remove Container').click();
await expect(page.getByRole('dialog', { name: 'Overlay' })).toContainText(
await expect(page.getByRole('dialog')).toHaveText(
'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();
expect(await containerHandles.count()).toEqual(2);
await page.getByRole('button', { name: 'OK' }).click();
expect(await page.getByRole('group', { name: 'Container' }).count()).toEqual(2);
});
test('Remove Frame', async ({ page }) => {
expect(await page.getByRole('group', { name: 'Frame' }).count()).toEqual(2);
await page.getByRole('group', { name: 'Child Layout 1' }).click();
await page.getByTitle('Remove Frame').click();
await expect(page.getByRole('dialog', { name: 'Overlay' })).toContainText(
await expect(page.getByRole('dialog')).toHaveText(
'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' }).click();
expect(await page.getByRole('group', { name: 'Frame' }).count()).toEqual(1);
});
test('Columns/Rows Layout Toggle', async ({ page }) => {
await page.getByRole('columnheader', { name: 'Container Handle 1' }).click();
const flexRows = page.getByLabel('Flexible Layout Row');
expect(await flexRows.count()).toEqual(0);
await page.getByRole('group', { name: 'Container' }).nth(1).click();
expect(await page.locator('.c-fl--rows').count()).toEqual(0);
await page.getByTitle('Columns layout').click();
expect(await flexRows.count()).toEqual(1);
expect(await page.locator('.c-fl--rows').count()).toEqual(1);
await page.getByTitle('Rows layout').click();
expect(await flexRows.count()).toEqual(0);
expect(await page.locator('.c-fl--rows').count()).toEqual(0);
});
});

View File

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

View File

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

View File

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

View File

@ -1,5 +1,5 @@
/*****************************************************************************
* Open MCT, Copyright (c) 2014-2024, United States Government
* Open MCT, Copyright (c) 2014-2023, United States Government
* as represented by the Administrator of the National Aeronautics and Space
* Administration. All rights reserved.
*
@ -20,29 +20,40 @@
* at runtime from the About dialog for additional information.
*****************************************************************************/
/**
* This test is dedicated to test conditional styling
/*
This test suite is dedicated to tests which verify the basic operations surrounding exportAsJSON.
*/
import { test } from '../../../../pluginFixtures.js';
// FIXME: Remove this eslint exception once tests are implemented
// eslint-disable-next-line no-unused-vars
const { test, expect } = require('../../../../baseFixtures');
test.describe('Conditional Styling', () => {
test.describe('ExportAsJSON', () => {
test.fixme(
'Conditional Styling can be applied to Flex Layout and its children',
'Create a basic object and verify that it can be exported as JSON from Tree',
async ({ page }) => {
//test
//Create domain object
//Save Domain Object
//Verify that the newly created domain object can be exported as JSON from the Tree
}
);
test.fixme(
'Conditional Styling can be applied to Overlay Plot and its children',
'Create a basic object and verify that it can be exported as JSON from 3 dot menu',
async ({ page }) => {
//test
//Create domain object
//Save Domain Object
//Verify that the newly created domain object can be exported as JSON from the 3 dot menu
}
);
test.fixme('Verify that a nested Object can be exported as JSON', async ({ page }) => {
// Create 2 objects with hierarchy
// Export as JSON
// Verify Hierarchy
});
test.fixme(
'Conditional Styling changes the styling of the object the condition changes state',
'Verify that the ExportAsJSON dropdown does not appear for the item X',
async ({ page }) => {
//test
// Other than non-persistable objects
}
);
});

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,5 +1,5 @@
/*****************************************************************************
* Open MCT, Copyright (c) 2014-2024, United States Government
* Open MCT, Copyright (c) 2014-2023, United States Government
* as represented by the Administrator of the National Aeronautics and Space
* Administration. All rights reserved.
*
@ -19,16 +19,15 @@
* this source code distribution or the Licensing information page available
* at runtime from the About dialog for additional information.
*****************************************************************************/
/* global __dirname */
/*
This test suite is dedicated to tests which verify the basic operations surrounding Notebooks.
*/
import { fileURLToPath } from 'url';
import { createDomainObjectWithDefaults } from '../../../../appActions.js';
import * as nbUtils from '../../../../helper/notebookUtils.js';
import { expect, streamToString, test } from '../../../../pluginFixtures.js';
const { test, expect, streamToString } = require('../../../../pluginFixtures');
const { createDomainObjectWithDefaults } = require('../../../../appActions');
const nbUtils = require('../../../../helper/notebookUtils');
const path = require('path');
const NOTEBOOK_NAME = 'Notebook';
@ -247,7 +246,7 @@ test.describe('Notebook export tests', () => {
test('can export notebook as text', async ({ page }) => {
await nbUtils.enterTextEntry(page, `Foo bar entry`);
// Click on 3 Dot Menu
await page.locator('button[title="More actions"]').click();
await page.locator('button[title="More options"]').click();
const downloadPromise = page.waitForEvent('download');
await page.getByRole('menuitem', { name: /Export Notebook as Text/ }).click();
@ -277,8 +276,9 @@ test.describe('Notebook entry tests', () => {
// Create Notebook with URL Whitelist
let notebookObject;
test.beforeEach(async ({ page }) => {
// eslint-disable-next-line no-undef
await page.addInitScript({
path: fileURLToPath(new URL('../../../../helper/addInitNotebookWithUrls.js', import.meta.url))
path: path.join(__dirname, '../../../../helper/', 'addInitNotebookWithUrls.js')
});
await page.goto('./', { waitUntil: 'domcontentloaded' });
@ -307,7 +307,7 @@ test.describe('Notebook entry tests', () => {
await page.goto(notebookObject.url);
// Reveal the notebook in the tree
await page.getByLabel('Show selected item in tree').click();
await page.getByTitle('Show selected item in tree').click();
await page
.getByRole('treeitem', { name: overlayPlot.name })
@ -331,7 +331,7 @@ test.describe('Notebook entry tests', () => {
await page.goto(notebookObject.url);
// Reveal the notebook in the tree
await page.getByLabel('Show selected item in tree').click();
await page.getByTitle('Show selected item in tree').click();
await nbUtils.enterTextEntry(page, 'Entry to drop into');
await page
@ -376,7 +376,7 @@ test.describe('Notebook entry tests', () => {
await page.goto(notebookObject.url);
// Reveal the notebook in the tree
await page.getByLabel('Show selected item in tree').click();
await page.getByTitle('Show selected item in tree').click();
await nbUtils.enterTextEntry(page, `This should be a link: ${TEST_LINK} is it?`);
@ -403,7 +403,7 @@ test.describe('Notebook entry tests', () => {
await page.goto(notebookObject.url);
// Reveal the notebook in the tree
await page.getByLabel('Show selected item in tree').click();
await page.getByTitle('Show selected item in tree').click();
await nbUtils.enterTextEntry(page, `This should NOT be a link: ${TEST_LINK} is it?`);
@ -420,7 +420,7 @@ test.describe('Notebook entry tests', () => {
await page.goto(notebookObject.url);
// Reveal the notebook in the tree
await page.getByLabel('Show selected item in tree').click();
await page.getByTitle('Show selected item in tree').click();
await nbUtils.enterTextEntry(page, `This should NOT be a link: ${TEST_LINK} is it?`);
@ -437,7 +437,7 @@ test.describe('Notebook entry tests', () => {
await page.goto(notebookObject.url);
// Reveal the notebook in the tree
await page.getByLabel('Show selected item in tree').click();
await page.getByTitle('Show selected item in tree').click();
await nbUtils.enterTextEntry(page, `This should be a link: ${INVALID_TEST_LINK} is it?`);
@ -454,7 +454,7 @@ test.describe('Notebook entry tests', () => {
await page.goto(notebookObject.url);
// Reveal the notebook in the tree
await page.getByLabel('Show selected item in tree').click();
await page.getByTitle('Show selected item in tree').click();
await nbUtils.enterTextEntry(page, `This should be a link: ${TEST_LINK} is it?`);
@ -482,7 +482,7 @@ test.describe('Notebook entry tests', () => {
await page.goto(notebookObject.url);
// Reveal the notebook in the tree
await page.getByLabel('Show selected item in tree').click();
await page.getByTitle('Show selected item in tree').click();
await nbUtils.enterTextEntry(
page,

View File

@ -0,0 +1,278 @@
/*****************************************************************************
* Open MCT, Copyright (c) 2014-2023, United States Government
* as represented by the Administrator of the National Aeronautics and Space
* Administration. All rights reserved.
*
* Open MCT 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.
*****************************************************************************/
/* global __dirname */
/*
This test suite is dedicated to tests which verify the basic operations surrounding Notebooks.
*/
const fs = require('fs').promises;
const path = require('path');
const { test, expect } = require('../../../../pluginFixtures');
const { createDomainObjectWithDefaults } = require('../../../../appActions');
const NOTEBOOK_NAME = 'Notebook';
test.describe('Snapshot Menu tests', () => {
test.fixme(
'When no default notebook is selected, Snapshot Menu dropdown should only have a single option',
async ({ page }) => {
// There should be no default notebook
// Clear default notebook if exists using `localStorage.setItem('notebook-storage', null);`
// refresh page
// Click on 'Notebook Snapshot Menu'
// 'save to Notebook Snapshots' should be only option there
}
);
test.fixme(
'When default notebook is updated selected, Snapshot Menu dropdown should list it as the newest option',
async ({ page }) => {
// Create 2a notebooks
// Set Notebook A as Default
// Open Snapshot Menu and note that Notebook A is listed
// Close Snapshot Menu
// Set Default Notebook to Notebook B
// Open Snapshot Notebook and note that Notebook B is listed
// Select Default Notebook Option and verify that Snapshot is added to Notebook B
}
);
test.fixme('Can add Snapshots via Snapshot Menu and details are correct', async ({ page }) => {
//Note this should be a visual test, too
// Create Telemetry object
// Create A notebook with many pages and sections.
// Set page and section defaults to be between first and last of many. i.e. 3 of 5
// Navigate to Telemetry object
// Select Default Notebook Option and verify that Snapshot is added to Notebook A
// Verify Snapshot Details appear correctly
});
test.fixme('Snapshots adjust time conductor', async ({ page }) => {
// Create Telemetry object
// Set Telemetry object's timeconductor to Fixed time with Start and End times are recorded
// Embed Telemetry object into notebook
// Set Time Conductor to Local clock
// Click into embedded telemetry object and verify object appears with same fixed time from record
});
});
test.describe('Snapshot Container tests', () => {
test.beforeEach(async ({ page }) => {
//Navigate to baseURL
await page.goto('./', { waitUntil: 'domcontentloaded' });
// 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.getByRole('button', { name: ' Snapshot ' }).click();
await page.getByRole('menuitem', { name: ' Save to Notebook Snapshots' }).click();
await page.getByRole('button', { name: 'Show' }).click();
});
test.fixme('5 Snapshots can be added to a container', async ({ page }) => {});
test.fixme(
'5 Snapshots can be added to a container and Deleted with Delete All action',
async ({ page }) => {}
);
test.fixme(
'A snapshot can be Deleted from Container with 3 dot action menu',
async ({ page }) => {}
);
test.fixme(
'A snapshot can be Viewed, Annotated, display deleted, and saved from Container with 3 dot action menu',
async ({ page }) => {
await page.locator('.c-snapshot.c-ne__embed').first().getByTitle('More options').click();
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('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 options').click();
await page.getByRole('menuitem', { name: 'Quick View' }).click();
await expect(page.locator('.c-overlay__outer')).toBeVisible();
});
test.fixme(
'A snapshot can be Navigated To from Container with 3 dot action menu',
async ({ page }) => {}
);
test.fixme(
'A snapshot can be Navigated To Item in Time from Container with 3 dot action menu',
async ({ page }) => {}
);
test.fixme('A snapshot Container can be open and closed', async ({ page }) => {});
test.fixme(
'Can add object to Snapshot container and pull into notebook and create a new entry',
async ({ page }) => {
//Create Notebook
//Create Telemetry Object
//From Telemetry Object, use 'save to Notebook Snapshots'
//Snapshots indicator should blink, click on it to view snapshots
//Navigate to Notebook
//Drag and Drop onto droppable area for new entry
//New Entry created with given snapshot added
//Snapshot removed from container?
}
);
test.fixme(
'Can add object to Snapshot container and pull into notebook and existing entry',
async ({ page }) => {
//Create Notebook
//Create Telemetry Object
//From Telemetry Object, use 'save to Notebook Snapshots'
//Snapshots indicator should blink, click on it to view snapshots
//Navigate to Notebook
//Drag and Drop into exiting entry
//Existing Entry updated with given snapshot
//Snapshot removed from container?
}
);
test.fixme(
'Verify Embedded options for PNG, JPG, and Annotate work correctly',
async ({ page }) => {
//Add snapshot to container
//Verify PNG, JPG, and Annotate buttons work correctly
}
);
});
test.describe('Snapshot image tests', () => {
test.beforeEach(async ({ page }) => {
//Navigate to baseURL
await page.goto('./', { waitUntil: 'domcontentloaded' });
// Create Notebook
await createDomainObjectWithDefaults(page, {
type: NOTEBOOK_NAME
});
});
test('Can drop an image onto a notebook and create a new entry', async ({ page }) => {
const imageData = await fs.readFile(
path.resolve(__dirname, '../../../../../src/images/favicons/favicon-96x96.png')
);
const imageArray = new Uint8Array(imageData);
const fileData = Array.from(imageArray);
const dropTransfer = await page.evaluateHandle((data) => {
const dataTransfer = new DataTransfer();
const file = new File([new Uint8Array(data)], 'favicon-96x96.png', { type: 'image/png' });
dataTransfer.items.add(file);
return dataTransfer;
}, fileData);
await page.dispatchEvent('.c-notebook__drag-area', 'drop', { dataTransfer: dropTransfer });
await page.locator('.c-ne__save-button > button').click();
// be sure that entry was created
await expect(page.getByText('favicon-96x96.png')).toBeVisible();
await page.getByRole('img', { name: 'favicon-96x96.png thumbnail' }).click();
// expect large image to be displayed
await expect(page.getByRole('dialog').getByText('favicon-96x96.png')).toBeVisible();
await page.getByLabel('Close').click();
// drop another image onto the entry
await page.dispatchEvent('.c-snapshots', 'drop', { dataTransfer: dropTransfer });
const secondThumbnail = page.getByRole('img', { name: 'favicon-96x96.png thumbnail' }).nth(1);
await secondThumbnail.waitFor({ state: 'attached' });
// expect two embedded images now
expect(await page.getByRole('img', { name: 'favicon-96x96.png thumbnail' }).count()).toBe(2);
await page.locator('.c-snapshot.c-ne__embed').first().getByTitle('More options').click();
await page.getByRole('menuitem', { name: /Remove This Embed/ }).click();
await page.getByRole('button', { name: 'Ok', exact: true }).click();
// Ensure that the thumbnail is removed before we assert
await secondThumbnail.waitFor({ state: 'detached' });
// expect one embedded image now as we deleted the other
expect(await page.getByRole('img', { name: 'favicon-96x96.png thumbnail' }).count()).toBe(1);
});
});
test.describe('Snapshot image failure tests', () => {
test.use({ failOnConsoleError: false });
test.beforeEach(async ({ page }) => {
//Navigate to baseURL
await page.goto('./', { waitUntil: 'domcontentloaded' });
// Create Notebook
await createDomainObjectWithDefaults(page, {
type: NOTEBOOK_NAME
});
});
test('Get an error notification when dropping unknown file onto notebook entry', async ({
page
}) => {
// fill Uint8Array array with some garbage data
const garbageData = new Uint8Array(100);
const fileData = Array.from(garbageData);
const dropTransfer = await page.evaluateHandle((data) => {
const dataTransfer = new DataTransfer();
const file = new File([new Uint8Array(data)], 'someGarbage.foo', { type: 'unknown/garbage' });
dataTransfer.items.add(file);
return dataTransfer;
}, fileData);
await page.dispatchEvent('.c-notebook__drag-area', 'drop', { dataTransfer: dropTransfer });
// should have gotten a notification from OpenMCT that we couldn't add it
await expect(page.getByText('Unknown object(s) dropped and cannot embed')).toBeVisible();
});
test('Get an error notification when dropping big files onto notebook entry', async ({
page
}) => {
const garbageSize = 15 * 1024 * 1024; // 15 megabytes
await page.addScriptTag({
// make the garbage client side
content: `window.bigGarbageData = new Uint8Array(${garbageSize})`
});
const bigDropTransfer = await page.evaluateHandle(() => {
const dataTransfer = new DataTransfer();
const file = new File([window.bigGarbageData], 'bigBoy.png', { type: 'image/png' });
dataTransfer.items.add(file);
return dataTransfer;
});
await page.dispatchEvent('.c-notebook__drag-area', 'drop', { dataTransfer: bigDropTransfer });
// should have gotten a notification from OpenMCT that we couldn't add it as it's too big
await expect(page.getByText('unable to embed')).toBeVisible();
});
});

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