diff --git a/.eslintrc.cjs b/.eslintrc.cjs
index bb8e6131..2ebc9b85 100644
--- a/.eslintrc.cjs
+++ b/.eslintrc.cjs
@@ -25,7 +25,7 @@ module.exports = {
"browser": true,
"es6": true,
"jasmine": true,
- "amd": true
+ "amd": false
},
"extends": "eslint:recommended",
"parser": "@babel/eslint-parser",
@@ -37,7 +37,16 @@ module.exports = {
"impliedStrict": true
}
},
+ "plugins": ['import'],
"rules": {
+ 'import/no-amd': 'error',
+ 'import/no-commonjs': 'error',
+ 'import/named': 'error',
+ 'import/no-webpack-loader-syntax': 'error',
+ 'import/first': 'error',
+ 'import/no-import-module-exports': 'error',
+ 'import/no-mutable-exports': 'error',
+ 'import/no-unused-modules': 'error',
"no-bitwise": "error",
"curly": "error",
"eqeqeq": "error",
@@ -237,6 +246,12 @@ module.exports = {
"varsIgnorePattern": "controller"
}
]
+ }
+ },
+ {
+ "files": ['*.eslintrc.cjs'],
+ "env": {
+ "node": true
}
}
]
diff --git a/.github/workflows/node.js.yml b/.github/workflows/node.js.yml
index 20e79ee3..cbd27434 100644
--- a/.github/workflows/node.js.yml
+++ b/.github/workflows/node.js.yml
@@ -1,4 +1,4 @@
-name: npm install and build:example
+name: npm install, build, and lint
on:
push:
@@ -31,3 +31,15 @@ jobs:
elif [ "${{ matrix.openmct-version }}" = "stable" ]; then
npm run build:example
fi
+
+ lint:
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v3
+ - name: Use Node.js 20.x
+ uses: actions/setup-node@v4
+ with:
+ node-version: 20.x
+ - run: npm install
+ - name: Run lint
+ run: npm run lint
diff --git a/.github/workflows/yamcs-quickstart-e2e.yml b/.github/workflows/yamcs-quickstart-e2e.yml
index 14294c6a..97c22179 100644
--- a/.github/workflows/yamcs-quickstart-e2e.yml
+++ b/.github/workflows/yamcs-quickstart-e2e.yml
@@ -21,7 +21,7 @@ jobs:
matrix:
yamcs-version:
- default
- - 5.8.3 #viper
+ - 5.8.7 #viper
## disabling until we get confirmation- 5.3.2 #ab
openmct-version:
- latest
@@ -74,9 +74,7 @@ jobs:
echo "Error: Unable to fetch Yamcs version. HTTP status code: $response"
exit 1
fi
- - name: Run Quickstart tests and publish to deploysentinel
- env:
- DEPLOYSENTINEL_API_KEY: ${{ secrets.DEPLOYSENTINEL_API_KEY }}
+ - name: Run Quickstart tests
run: npm run test:e2e:quickstart
- name: Capture docker logs to file
if: always()
diff --git a/.gitignore b/.gitignore
index 877200a5..5f9eecfc 100644
--- a/.gitignore
+++ b/.gitignore
@@ -12,3 +12,4 @@ test-results
# Misc
.DS_Store
+.vscode/settings.json
diff --git a/.webpack/webpack.common.js b/.webpack/webpack.common.js
index 2d445253..6191ea59 100644
--- a/.webpack/webpack.common.js
+++ b/.webpack/webpack.common.js
@@ -25,15 +25,14 @@ import { fileURLToPath } from 'node:url';
const projectRootDir = fileURLToPath(new URL('../', import.meta.url));
-// eslint-disable no-undef
-const WEBPACK_COMMON_CONFIG = {
+/** @type {import('webpack').Configuration} */
+const commonConfig = {
+ context: projectRootDir,
performance: {
hints: false
},
- resolve: {
- alias: {
- saveAs: "file-saver/src/FileSaver.js",
- }
+ entry: {
+ 'openmct-yamcs': './src/openmct-yamcs.js'
},
module: {
rules: [
@@ -42,16 +41,19 @@ const WEBPACK_COMMON_CONFIG = {
enforce: "pre",
use: ["source-map-loader"]
}
- ]
+ ],
},
output: {
globalObject: "this",
filename: '[name].js',
- // eslint-disable-next-line no-undef
path: path.resolve(projectRootDir, 'dist'),
- libraryTarget: 'umd',
- library: 'openmctYamcs'
+ library: {
+ type: 'umd',
+ export: 'default',
+ name: 'openmctYamcs'
+ }
}
};
-export default WEBPACK_COMMON_CONFIG;
+
+export default commonConfig;
diff --git a/.webpack/webpack.dev.js b/.webpack/webpack.dev.js
index c33ba8ee..84a9f5b8 100644
--- a/.webpack/webpack.dev.js
+++ b/.webpack/webpack.dev.js
@@ -19,30 +19,29 @@
* this source code distribution or the Licensing information page available
* at runtime from the About dialog for additional information.
*****************************************************************************/
-
import path from 'path';
+import { fileURLToPath } from 'url';
import { merge } from 'webpack-merge';
-import common from './webpack.common.js';
-import { fileURLToPath } from 'node:url';
+import commonConfig from './webpack.common.js';
+
+// Replicate __dirname functionality for ES modules
+const __dirname = path.dirname(fileURLToPath(import.meta.url));
-const projectRootDir = fileURLToPath(new URL('../', import.meta.url));
-export default merge(common, {
- context: projectRootDir,
+/** @type {import('webpack').Configuration} */
+const devConfig = {
mode: 'development',
devtool: 'eval-source-map',
entry: {
- 'openmct-yamcs-example': path.resolve(projectRootDir, 'example/index.js')
+ 'openmct-yamcs-example': './example/index.js'
},
devServer: {
compress: true,
port: 9000,
static: [{
- // eslint-disable-next-line no-undef
- directory: path.join(projectRootDir, 'example')
+ directory: path.join(__dirname, '../example'),
}, {
- // eslint-disable-next-line no-undef
- directory: path.join(projectRootDir, '/node_modules/openmct/dist'),
- publicPath: '/node_modules/openmct/dist'
+ directory: path.join(__dirname, '../node_modules/openmct/dist'),
+ publicPath: '/dist',
}],
proxy: {
"/yamcs-proxy/*": {
@@ -59,5 +58,12 @@ export default merge(common, {
pathRewrite: { '^/yamcs-proxy-ws/': '' }
}
}
+ },
+ resolve: {
+ alias: {
+ openmct: path.resolve(__dirname, '../node_modules/openmct/dist/openmct.js')
+ }
}
-});
+};
+
+export default merge(commonConfig, devConfig);
diff --git a/.webpack/webpack.prod.js b/.webpack/webpack.prod.js
index c1bcba9d..46bc12dd 100644
--- a/.webpack/webpack.prod.js
+++ b/.webpack/webpack.prod.js
@@ -20,18 +20,12 @@
* at runtime from the About dialog for additional information.
*****************************************************************************/
-import path from 'path';
import { merge } from 'webpack-merge';
import common from './webpack.common.js';
-import { fileURLToPath } from 'node:url';
-const projectRootDir = fileURLToPath(new URL('../', import.meta.url));
-
-export default merge(common, {
- context: projectRootDir,
+/** @type {import('webpack').Configuration} */
+const prodConfig = {
mode: 'production',
- entry: {
- 'openmct-yamcs': path.resolve(projectRootDir, 'src/plugin.js')
- },
devtool: 'source-map'
-});
+}
+export default merge(common, prodConfig);
diff --git a/README.md b/README.md
index 7f7c315a..4c0e07f7 100644
--- a/README.md
+++ b/README.md
@@ -91,6 +91,24 @@ openmct.install(installYamcsPlugin({
| yamcsInstance | The name of the instance configured in YAMCS that you wish to connect to. | myproject |
| yamcsFolder | The name of the instance configured in YAMCS that you wish to connect to. | myproject |
+## dictionaryRequestCacheStrategyPromise
+installYamcsPlugin also accepts an optional promise argument `dictionaryRequestCacheStrategyPromise`. This strategy is passed to the request for loading the YAMCS dictionary, or the `#loadTelemetryDictionary` function in `object-provider.js`. An example of how to make use of this is below.
+```
+let cacheStrategy;
+const cacheStrategyPromise = new Promise(resolve => cacheStrategy)
+
+openmct.install(installYamcsPlugin(
+ configuration,
+ cacheStrategryPromise
+))
+
+if (DICTIONARY_VERSION_IS_NEW) { // some check to determine dictionary version
+ cacheStrategy({cache: 'reload'})
+} else {
+ cacheStrategy({})
+}
+```
+
## Special XTCE features
If you are using an XTCE configuration in Yamcs, there are two special
diff --git a/example/index.html b/example/index.html
index 239e1011..29ce5701 100644
--- a/example/index.html
+++ b/example/index.html
@@ -21,7 +21,7 @@
Open MCT - YAMCS Example
-
+
diff --git a/example/index.js b/example/index.js
index 50168adc..a88b8997 100644
--- a/example/index.js
+++ b/example/index.js
@@ -1,5 +1,5 @@
-import installYamcsPlugin from '../src/plugin.js';
+import installYamcsPlugin from '../src/openmct-yamcs.js';
const config = {
"yamcsDictionaryEndpoint": "http://localhost:9000/yamcs-proxy/",
@@ -8,7 +8,9 @@ const config = {
"yamcsUserEndpoint": "http://localhost:9000/yamcs-proxy/api/user/",
"yamcsInstance": "myproject",
"yamcsProcessor": "realtime",
- "yamcsFolder": "myproject"
+ "yamcsFolder": "myproject",
+ "throttleRate": 1000,
+ "maxBatchSize": 15
};
const STATUS_STYLES = {
"NO_STATUS": {
@@ -39,57 +41,55 @@ const STATUS_STYLES = {
};
const openmct = window.openmct;
-(function () {
- const THIRTY_MINUTES = 30 * 60 * 1000;
+const THIRTY_MINUTES = 30 * 60 * 1000;
- openmct.setAssetPath('/node_modules/openmct/dist');
+openmct.setAssetPath('/dist');
- installDefaultPlugins();
- openmct.install(installYamcsPlugin(config));
- openmct.install(openmct.plugins.OperatorStatus({statusStyles: STATUS_STYLES}));
+installDefaultPlugins();
+openmct.install(installYamcsPlugin(config));
+openmct.install(openmct.plugins.OperatorStatus({statusStyles: STATUS_STYLES}));
- document.addEventListener('DOMContentLoaded', function () {
- openmct.start();
- });
+document.addEventListener('DOMContentLoaded', function () {
+ openmct.start();
+});
- function installDefaultPlugins() {
- openmct.install(openmct.plugins.LocalStorage());
- openmct.install(openmct.plugins.Espresso());
- openmct.install(openmct.plugins.MyItems());
- openmct.install(openmct.plugins.example.Generator());
- openmct.install(openmct.plugins.example.ExampleImagery());
- openmct.install(openmct.plugins.UTCTimeSystem());
- openmct.install(openmct.plugins.TelemetryMean());
+function installDefaultPlugins() {
+ openmct.install(openmct.plugins.LocalStorage());
+ openmct.install(openmct.plugins.Espresso());
+ openmct.install(openmct.plugins.MyItems());
+ openmct.install(openmct.plugins.example.Generator());
+ openmct.install(openmct.plugins.example.ExampleImagery());
+ openmct.install(openmct.plugins.UTCTimeSystem());
+ openmct.install(openmct.plugins.TelemetryMean());
- openmct.install(openmct.plugins.DisplayLayout({
- showAsView: ['summary-widget', 'example.imagery', 'yamcs.image']
- }));
- openmct.install(openmct.plugins.Conductor({
- menuOptions: [
- {
- name: "Realtime",
- timeSystem: 'utc',
- clock: 'local',
- clockOffsets: {
- start: -THIRTY_MINUTES,
- end: 0
- }
- },
- {
- name: "Fixed",
- timeSystem: 'utc',
- bounds: {
- start: Date.now() - THIRTY_MINUTES,
- end: 0
- }
+ openmct.install(openmct.plugins.DisplayLayout({
+ showAsView: ['summary-widget', 'example.imagery', 'yamcs.image']
+ }));
+ openmct.install(openmct.plugins.Conductor({
+ menuOptions: [
+ {
+ name: "Realtime",
+ timeSystem: 'utc',
+ clock: 'local',
+ clockOffsets: {
+ start: -THIRTY_MINUTES,
+ end: 0
}
- ]
- }));
- openmct.install(openmct.plugins.SummaryWidget());
- openmct.install(openmct.plugins.Notebook());
- openmct.install(openmct.plugins.LADTable());
- openmct.install(openmct.plugins.ClearData(['table', 'telemetry.plot.overlay', 'telemetry.plot.stacked']));
+ },
+ {
+ name: "Fixed",
+ timeSystem: 'utc',
+ bounds: {
+ start: Date.now() - THIRTY_MINUTES,
+ end: 0
+ }
+ }
+ ]
+ }));
+ openmct.install(openmct.plugins.SummaryWidget());
+ openmct.install(openmct.plugins.Notebook());
+ openmct.install(openmct.plugins.LADTable());
+ openmct.install(openmct.plugins.ClearData(['table', 'telemetry.plot.overlay', 'telemetry.plot.stacked']));
- openmct.install(openmct.plugins.FaultManagement());
- }
-}());
+ openmct.install(openmct.plugins.FaultManagement());
+}
diff --git a/package.json b/package.json
index 9779bf52..cbcc1613 100644
--- a/package.json
+++ b/package.json
@@ -20,6 +20,7 @@
"test:e2e:smoke": "npx playwright test --config=./tests/e2e/playwright-quickstart.config.js --project=chromium quickstartSmoke",
"test:e2e:quickstart": "npx playwright test --config=./tests/e2e/playwright-quickstart.config.js --project=chromium tests/e2e/yamcs/",
"test:e2e:quickstart:local": "npx playwright test --config=./tests/e2e/playwright-quickstart.config.js --project=local-chrome tests/e2e/yamcs/",
+ "test:e2e:watch": "npx playwright test --ui --config=./tests/e2e/playwright-quickstart.config.js",
"wait-for-yamcs": "wait-on http-get://localhost:8090/ -v"
},
"keywords": [
@@ -37,11 +38,11 @@
"devDependencies": {
"@babel/core": "7.20.12",
"@babel/eslint-parser": "7.19.1",
- "@deploysentinel/playwright": "0.3.4",
"@playwright/test": "1.39.0",
"babel-loader": "9.1.0",
"babel-plugin-istanbul": "6.1.1",
"eslint": "8.38.0",
+ "eslint-plugin-import":"2.29.1",
"eventemitter3": "4.0.7",
"file-saver": "2.0.5",
"semver": "7.5.2",
diff --git a/src/actions/exportToCSV/ExportToCSVAction.js b/src/actions/exportToCSV/ExportToCSVAction.js
index 31a50b83..4089de39 100644
--- a/src/actions/exportToCSV/ExportToCSVAction.js
+++ b/src/actions/exportToCSV/ExportToCSVAction.js
@@ -21,7 +21,7 @@
*****************************************************************************/
import { OBJECT_TYPES } from "../../const.js";
import {idToQualifiedName} from "../../utils.js";
-import {saveAs} from 'saveAs';
+import { saveAs } from 'file-saver';
const SUPPORTED_TYPES = [OBJECT_TYPES.TELEMETRY_OBJECT_TYPE, OBJECT_TYPES.AGGREGATE_TELEMETRY_TYPE];
diff --git a/src/plugin.js b/src/openmct-yamcs.js
similarity index 94%
rename from src/plugin.js
rename to src/openmct-yamcs.js
index 5a7da0da..249d19e9 100644
--- a/src/plugin.js
+++ b/src/openmct-yamcs.js
@@ -1,5 +1,5 @@
/*****************************************************************************
- * Open MCT, Copyright (c) 2014-2022, United States Government
+ * Open MCT, Copyright (c) 2014-2024, United States Government
* as represented by the Administrator of the National Aeronautics and Space
* Administration. All rights reserved.
*
@@ -36,11 +36,13 @@ import OperatorStatusTelemetry from './providers/user/operator-status-telemetry.
import LatestTelemetryProvider from './providers/latest-telemetry-provider.js';
import PollQuestionParameter from './providers/user/poll-question-parameter.js';
import PollQuestionTelemetry from './providers/user/poll-question-telemetry.js';
-import ExportToCSVActionPlugin from "./actions/exportToCSV/plugin.js";
-
-export default function installYamcsPlugin(configuration) {
- return function install(openmct) {
+import ExportToCSVActionPlugin from './actions/exportToCSV/plugin.js';
+export default function install(
+ configuration,
+ dictionaryRequestCacheStrategyPromise
+) {
+ return (openmct) => {
openmct.install(openmct.plugins.ISOTimeFormat());
const latestTelemetryProvider = new LatestTelemetryProvider({
@@ -58,9 +60,12 @@ export default function installYamcsPlugin(configuration) {
openmct.telemetry.addProvider(historicalTelemetryProvider);
const realtimeTelemetryProvider = new RealtimeProvider(
+ openmct,
configuration.yamcsWebsocketEndpoint,
configuration.yamcsInstance,
- configuration.yamcsProcessor
+ configuration.yamcsProcessor,
+ configuration.throttleRate,
+ configuration.maxBatchSize
);
openmct.telemetry.addProvider(realtimeTelemetryProvider);
realtimeTelemetryProvider.connect();
@@ -127,7 +132,8 @@ export default function installYamcsPlugin(configuration) {
pollQuestionParameter,
pollQuestionTelemetry,
realtimeTelemetryProvider,
- configuration.yamcsProcessor
+ configuration.yamcsProcessor,
+ dictionaryRequestCacheStrategyPromise
);
openmct.objects.addRoot({
diff --git a/src/providers/limit-provider.js b/src/providers/limit-provider.js
index 54722ed6..e864c188 100644
--- a/src/providers/limit-provider.js
+++ b/src/providers/limit-provider.js
@@ -100,7 +100,7 @@ export default class LimitProvider {
const limits = domainObject.configuration.limits;
return {
- limits: async () => limits
+ limits: () => Promise.resolve(limits)
};
}
diff --git a/src/providers/object-provider.js b/src/providers/object-provider.js
index de499d49..81891a49 100644
--- a/src/providers/object-provider.js
+++ b/src/providers/object-provider.js
@@ -39,7 +39,7 @@ const YAMCS_API_MAP = {
const operatorStatusParameter = new OperatorStatusParameter();
export default class YamcsObjectProvider {
- constructor(openmct, url, instance, folderName, roleStatusTelemetry, pollQuestionParameter, pollQuestionTelemetry, realtimeTelemetryProvider, processor = 'realtime') {
+ constructor(openmct, url, instance, folderName, roleStatusTelemetry, pollQuestionParameter, pollQuestionTelemetry, realtimeTelemetryProvider, processor = 'realtime', dictionaryRequestCacheStrategyPromise = Promise.resolve({})) {
this.openmct = openmct;
this.url = url;
this.instance = instance;
@@ -52,6 +52,7 @@ export default class YamcsObjectProvider {
this.dictionary = {};
this.limitOverrides = {};
this.dictionaryPromise = null;
+ this.dictionaryRequestCacheStrategyPromise = dictionaryRequestCacheStrategyPromise;
this.roleStatusTelemetry = roleStatusTelemetry;
this.pollQuestionParameter = pollQuestionParameter;
this.pollQuestionTelemetry = pollQuestionTelemetry;
@@ -180,7 +181,7 @@ export default class YamcsObjectProvider {
#getTelemetryDictionary() {
if (!this.dictionaryPromise) {
- this.dictionaryPromise = this.#loadTelemetryDictionary(this.url, this.instance, this.folderName)
+ this.dictionaryPromise = this.#loadTelemetryDictionary()
.finally(() => {
this.roleStatusTelemetry.dictionaryLoadComplete();
});
@@ -193,8 +194,10 @@ export default class YamcsObjectProvider {
const operation = 'parameters?details=yes&limit=1000';
const parameterUrl = this.url + 'api/mdb/' + this.instance + '/' + operation;
const url = this.#getMdbUrl('space-systems');
- const spaceSystems = await accumulateResults(url, {}, 'spaceSystems', []);
- const parameters = await accumulateResults(parameterUrl, {}, 'parameters', []);
+ const requestOptions = await this.dictionaryRequestCacheStrategyPromise;
+
+ const spaceSystems = await accumulateResults(url, requestOptions, 'spaceSystems', []);
+ const parameters = await accumulateResults(parameterUrl, requestOptions, 'parameters', []);
/* Sort the space systems by name, so that the
children of the root object are in sorted order. */
diff --git a/src/providers/realtime-provider.js b/src/providers/realtime-provider.js
index 42c8da98..61c78af8 100644
--- a/src/providers/realtime-provider.js
+++ b/src/providers/realtime-provider.js
@@ -20,7 +20,7 @@
* at runtime from the About dialog for additional information.
*****************************************************************************/
-import * as MESSAGES from './messages.js';
+import { SUBSCRIBE, UNSUBSCRIBE } from './messages.js';
import {
OBJECT_TYPES,
DATA_TYPES,
@@ -33,7 +33,6 @@ import {
import {
buildStalenessResponseObject,
idToQualifiedName,
- qualifiedNameToId,
getValue,
addLimitInformation,
getLimitFromAlarmRange
@@ -41,9 +40,11 @@ import {
import { commandToTelemetryDatum } from './commands.js';
import { eventToTelemetryDatum, eventShouldBeFiltered } from './events.js';
-const FALLBACK_AND_WAIT_MS = [1000, 5000, 5000, 10000, 10000, 30000];
export default class RealtimeProvider {
- constructor(url, instance, processor = 'realtime') {
+ #socketWorker = null;
+ #openmct;
+
+ constructor(openmct, url, instance, processor = 'realtime', rate = 1000, maxBatchSize = 15) {
this.url = url;
this.instance = instance;
this.processor = processor;
@@ -58,9 +59,56 @@ export default class RealtimeProvider {
this.lastSubscriptionId = 1;
this.subscriptionsByCall = new Map();
this.subscriptionsById = {};
+ this.#socketWorker = new openmct.telemetry.BatchingWebSocket(openmct);
+ this.#openmct = openmct;
+ this.#setBatchingStrategy(rate, maxBatchSize);
this.addSupportedObjectTypes(Object.values(OBJECT_TYPES));
this.addSupportedDataTypes(Object.values(DATA_TYPES));
+ const setCallFromClockIfNecessary = this.#setCallFromClockIfNecessary.bind(this);
+
+ openmct.time.on('clock', setCallFromClockIfNecessary);
+
+ openmct.once('destroy', () => {
+ openmct.time.off('clock', setCallFromClockIfNecessary);
+ });
+ }
+
+ #setCallFromClockIfNecessary(clock) {
+ if (clock === undefined) {
+ this.unsetCall();
+ }
+
+ if (clock.key === 'remote-clock') {
+ this.#setCallFromClock(clock);
+ }
+ }
+ #setBatchingStrategy(rate, maxBatchSize) {
+ // This strategy batches parameter value messages
+ this.#socketWorker.setBatchingStrategy({
+ /* istanbul ignore next */
+ shouldBatchMessage: /* istanbul ignore next */ (message) => {
+ // If a parameter value message, the message type will be "parameters"
+ // The type field is always located at a character offset of 13 and
+ // if it is "parameters" will be 10 characters long.
+ const type = message.substring(13, 23);
+
+ return type === 'parameters';
+ },
+ /* istanbul ignore next */
+ getBatchIdFromMessage: /* istanbul ignore next */ (message) => {
+ // Only dealing with "parameters" messages at this point. The call number
+ // identifies the parameter, and is used for batching. Will be located
+ // at a character offset of 36. Because it is of indeterminate length
+ // (we don't know the number) we have to do a sequential search forward
+ // from the 37th character for a terminating ",".
+ const callNumber = message.substring(36, message.indexOf(",", 37));
+
+ return callNumber;
+ }
+ });
+ this.#socketWorker.setRate(rate);
+ this.#socketWorker.setMaxBatchSize(maxBatchSize);
}
addSupportedObjectTypes(types) {
@@ -70,7 +118,6 @@ export default class RealtimeProvider {
addSupportedDataTypes(dataTypes) {
dataTypes.forEach(dataType => this.supportedDataTypes[dataType] = dataType);
}
-
supportsSubscribe(domainObject) {
return this.isSupportedObjectType(domainObject.type);
}
@@ -112,7 +159,7 @@ export default class RealtimeProvider {
if (subscriptionDetails) {
this.sendUnsubscribeMessage(subscriptionDetails);
- this.subscriptionsByCall.delete(subscriptionDetails.call);
+ this.subscriptionsByCall.delete(subscriptionDetails.call.toString());
delete this.subscriptionsById[id];
}
};
@@ -137,7 +184,7 @@ export default class RealtimeProvider {
this.sendUnsubscribeMessage(subscriptionDetails);
if (this.subscriptionsById[id]) {
- this.subscriptionsByCall.delete(this.subscriptionsById[id].call);
+ this.subscriptionsByCall.delete(this.subscriptionsById[id].call.toString());
delete this.subscriptionsById[id];
}
};
@@ -161,46 +208,78 @@ export default class RealtimeProvider {
sendSubscribeMessage(subscriptionDetails) {
const domainObject = subscriptionDetails.domainObject;
- const message = MESSAGES.SUBSCRIBE[domainObject.type](subscriptionDetails);
+ const message = SUBSCRIBE[domainObject.type](subscriptionDetails);
- this.sendOrQueueMessage(message);
+ this.sendMessage(message);
}
sendUnsubscribeMessage(subscriptionDetails) {
- let message = MESSAGES.UNSUBSCRIBE(subscriptionDetails);
+ let message = UNSUBSCRIBE(subscriptionDetails);
- this.sendOrQueueMessage(message);
+ this.sendMessage(message);
}
- reconnect() {
- this.subscriptionsByCall.clear();
+ #setCallFromClock(clock) {
+ const correspondingSubscription = Object.values(this.subscriptionsById).find(subscription => {
+ return subscription.domainObject.identifier.key === clock.identifier.key;
+ });
- if (this.reconnectTimeout) {
- return;
+ if (correspondingSubscription !== undefined) {
+ this.remoteClockCallNumber = correspondingSubscription.call.toString();
+ } else {
+ delete this.remoteClockCallNumber;
}
+ }
- this.reconnectTimeout = setTimeout(() => {
- this.connect();
- delete this.reconnectTimeout;
- }, FALLBACK_AND_WAIT_MS[this.currentWaitIndex]);
+ #processBatchQueue(batchQueue, call) {
+ let subscriptionDetails = this.subscriptionsByCall.get(call);
+ let telemetryData = [];
- if (this.currentWaitIndex < FALLBACK_AND_WAIT_MS.length - 1) {
- this.currentWaitIndex++;
+ // possibly cancelled
+ if (!subscriptionDetails) {
+ return;
}
- }
- sendOrQueueMessage(request) {
- if (this.connected) {
- try {
- this.sendMessage(request);
- } catch (error) {
- this.connected = false;
- this.requests.push(request);
- console.error("🚨 Error while attempting to send to websocket, closing websocket", error);
- this.socket.close();
- }
- } else {
- this.requests.push(request);
+ batchQueue.forEach((rawMessage) => {
+ const message = JSON.parse(rawMessage);
+ const values = message.data.values || [];
+ const parentName = subscriptionDetails.domainObject.name;
+
+ values.forEach(parameter => {
+ let datum = {
+ timestamp: parameter[METADATA_TIME_KEY]
+ };
+ const value = getValue(parameter, parentName);
+
+ if (this.observingStaleness[subscriptionDetails.name] !== undefined) {
+ const status = STALENESS_STATUS_MAP[parameter.acquisitionStatus];
+
+ if (this.observingStaleness[subscriptionDetails.name].response.isStale !== status) {
+ const stalenesResponseObject = buildStalenessResponseObject(
+ status,
+ parameter[METADATA_TIME_KEY]
+ );
+ this.observingStaleness[subscriptionDetails.name].response = stalenesResponseObject;
+ this.observingStaleness[subscriptionDetails.name].callback(stalenesResponseObject);
+ }
+ }
+
+ if (parameter.engValue.type !== AGGREGATE_TYPE) {
+ datum.value = value;
+ } else {
+ datum = {
+ ...datum,
+ ...value
+ };
+ }
+
+ addLimitInformation(parameter, datum);
+ telemetryData.push(datum);
+ });
+ });
+
+ if (telemetryData.length > 0) {
+ subscriptionDetails.callback(telemetryData);
}
}
@@ -212,80 +291,65 @@ export default class RealtimeProvider {
let wsUrl = `${this.url}`;
this.lastSubscriptionId = 1;
this.connected = false;
- this.socket = new WebSocket(wsUrl);
- this.socket.onopen = () => {
- clearTimeout(this.reconnectTimeout);
+ this.#socketWorker.connect(wsUrl);
- this.connected = true;
- console.debug(`🔌 Established websocket connection to ${wsUrl}`);
-
- this.currentWaitIndex = 0;
+ this.#socketWorker.addEventListener('error', () => {
this.resubscribeToAll();
- this.flushQueue();
- };
+ });
+
+ this.#socketWorker.addEventListener('batch', (batchEvent) => {
+ const batch = batchEvent.detail;
- this.socket.onmessage = (event) => {
- const message = JSON.parse(event.data);
+ let remoteClockValue;
+ // If remote clock active, process its value before any telemetry values to ensure the bounds are always up to date.
+ if (this.remoteClockCallNumber !== undefined) {
+ remoteClockValue = batch[this.remoteClockCallNumber];
+ if (remoteClockValue !== undefined) {
+ this.#processBatchQueue(batch[this.remoteClockCallNumber], this.remoteClockCallNumber);
+ // Delete so we don't process it twice.
+ delete batch[this.remoteClockCallNumber];
+ }
+ }
+
+ Object.keys(batch).forEach((call) => {
+ this.#processBatchQueue(batch[call], call);
+ });
+ });
+
+ this.#socketWorker.addEventListener('message', (messageEvent) => {
+ const message = JSON.parse(messageEvent.detail);
if (!this.isSupportedDataType(message.type)) {
return;
}
const isReply = message.type === DATA_TYPES.DATA_TYPE_REPLY;
+ const call = message.call;
let subscriptionDetails;
if (isReply) {
const id = message.data.replyTo;
- const call = message.call;
subscriptionDetails = this.subscriptionsById[id];
subscriptionDetails.call = call;
- this.subscriptionsByCall.set(call, subscriptionDetails);
+ // Subsequent retrieval uses a string, so for performance reasons we use a string as a key.
+ this.subscriptionsByCall.set(call.toString(), subscriptionDetails);
+
+ const remoteClockIdentifier = this.#openmct.time.getClock()?.identifier;
+ const isRemoteClockActive = remoteClockIdentifier !== undefined;
+
+ if (isRemoteClockActive && subscriptionDetails.domainObject.identifier.key === remoteClockIdentifier.key) {
+ this.remoteClockCallNumber = call.toString();
+ }
} else {
- subscriptionDetails = this.subscriptionsByCall.get(message.call);
+ subscriptionDetails = this.subscriptionsByCall.get(message.call.toString());
// possibly cancelled
if (!subscriptionDetails) {
return;
}
- if (this.isTelemetryMessage(message)) {
- let values = message.data.values || [];
- let parentName = subscriptionDetails.domainObject.name;
-
- values.forEach(parameter => {
- let datum = {
- id: qualifiedNameToId(subscriptionDetails.name),
- timestamp: parameter[METADATA_TIME_KEY]
- };
- let value = getValue(parameter, parentName);
-
- if (this.observingStaleness[subscriptionDetails.name] !== undefined) {
- const status = STALENESS_STATUS_MAP[parameter.acquisitionStatus];
-
- if (this.observingStaleness[subscriptionDetails.name].response.isStale !== status) {
- const stalenesResponseObject = buildStalenessResponseObject(
- status,
- parameter[METADATA_TIME_KEY]
- );
- this.observingStaleness[subscriptionDetails.name].response = stalenesResponseObject;
- this.observingStaleness[subscriptionDetails.name].callback(stalenesResponseObject);
- }
- }
-
- if (parameter.engValue.type !== AGGREGATE_TYPE) {
- datum.value = value;
- } else {
- datum = {
- ...datum,
- ...value
- };
- }
-
- addLimitInformation(parameter, datum);
- subscriptionDetails.callback(datum);
- });
- } else if (this.isCommandMessage(message)) {
+ if (this.isCommandMessage(message)) {
const datum = commandToTelemetryDatum(message.data);
subscriptionDetails.callback(datum);
} else if (this.isEventMessage(message)) {
@@ -312,20 +376,7 @@ export default class RealtimeProvider {
subscriptionDetails.callback(message.data);
}
}
- };
-
- this.socket.onerror = (error) => {
- console.error(`🚨 Websocket error, closing websocket`, error);
- this.socket.close();
- };
-
- this.socket.onclose = () => {
- console.warn('🚪 Websocket closed. Attempting to reconnect...');
- this.connected = false;
- this.socket = null;
-
- this.reconnect();
- };
+ });
}
resubscribeToAll() {
@@ -334,30 +385,8 @@ export default class RealtimeProvider {
});
}
- flushQueue() {
- let shouldCloseWebsocket = false;
- this.requests = this.requests.filter((request) => {
- try {
- this.sendMessage(request);
- } catch (error) {
- this.connected = false;
- console.error('🚨 Error while attempting to send to websocket, closing websocket', error);
-
- shouldCloseWebsocket = true;
-
- return true;
- }
-
- return false;
- });
-
- if (shouldCloseWebsocket) {
- this.socket.close();
- }
- }
-
sendMessage(message) {
- this.socket.send(message);
+ this.#socketWorker.sendMessage(message);
}
isTelemetryMessage(message) {
diff --git a/tests/e2e/playwright-quickstart.config.js b/tests/e2e/playwright-quickstart.config.js
index fb91bd99..c7cbe537 100644
--- a/tests/e2e/playwright-quickstart.config.js
+++ b/tests/e2e/playwright-quickstart.config.js
@@ -64,9 +64,7 @@ const config = {
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' }],
- ['@deploysentinel/playwright']
- ]
+ ['junit', { outputFile: 'test-results/results.xml' }]]
};
export default config;
diff --git a/tests/e2e/yamcs/historicalData.e2e.spec.js b/tests/e2e/yamcs/historicalData.e2e.spec.js
index d94d60a7..a17642eb 100644
--- a/tests/e2e/yamcs/historicalData.e2e.spec.js
+++ b/tests/e2e/yamcs/historicalData.e2e.spec.js
@@ -24,8 +24,8 @@
Network Specific Tests
*/
-import { test, expect } from '../opensource/pluginFixtures';
-import { setFixedTimeMode } from '../opensource/appActions';
+import { test, expect } from '../opensource/pluginFixtures.js';
+import { setFixedTimeMode } from '../opensource/appActions.js';
test.describe("Samples endpoint with useRawValue search param @yamcs", () => {
// Collect all request events, specifically for YAMCS
diff --git a/tests/e2e/yamcs/limits.e2e.spec.js b/tests/e2e/yamcs/limits.e2e.spec.js
index 9513f9c2..c7144c41 100644
--- a/tests/e2e/yamcs/limits.e2e.spec.js
+++ b/tests/e2e/yamcs/limits.e2e.spec.js
@@ -24,8 +24,8 @@
MDB Limits Specific Tests
*/
-import { test, expect } from '../opensource/pluginFixtures';
-import { createDomainObjectWithDefaults, waitForPlotsToRender } from '../opensource/appActions';
+import { test, expect } from '../opensource/pluginFixtures.js';
+import { createDomainObjectWithDefaults, waitForPlotsToRender } from '../opensource/appActions.js';
test.describe("Mdb runtime limits tests @yamcs", () => {
test('Can show mdb limits when changed', async ({ page }) => {
@@ -51,22 +51,22 @@ test.describe("Mdb runtime limits tests @yamcs", () => {
const detectorTreeItem = page.getByRole('treeitem', { name: /Detector_Temp/ });
// Enter edit mode for the overlay plot
- await page.click('button[title="Edit"]');
+ await page.getByLabel('Edit Object').click();
//Drag and drop the Detector_Temp telemetry endpoint into this overlay plot
const objectPane = page.locator('.c-object-view');
await detectorTreeItem.dragTo(objectPane);
// Save (exit edit mode)
- await page.locator('button[title="Save"]').click();
- await page.locator('li[title="Save and Finish Editing"]').click();
+ await page.getByRole('button', { name: 'Save' }).click();
+ await page.getByRole('listitem', { name: 'Save and Finish Editing' }).click();
// Assert that no limit lines are shown by default
await page.waitForSelector('.js-limit-area', { state: 'attached' });
expect(await page.locator('.c-plot-limit-line').count()).toBe(0);
// Enter edit mode
- await page.click('button[title="Edit"]');
+ await page.getByLabel('Edit Object').click();
// Expand the "Detector_Temp" plot series options and enable limit lines
await page.getByRole('tab', { name: 'Config' }).click();
@@ -81,8 +81,8 @@ test.describe("Mdb runtime limits tests @yamcs", () => {
.check();
// Save (exit edit mode)
- await page.locator('button[title="Save"]').click();
- await page.locator('li[title="Save and Finish Editing"]').click();
+ await page.getByRole('button', { name: 'Save' }).click();
+ await page.getByRole('listitem', { name: 'Save and Finish Editing' }).click();
// Change the limits for the Detector_Temp parameter using the yamcs API)
const runTimeLimitChangeResponse = await page.request.patch('http://localhost:8090/api/mdb-overrides/myproject/realtime/parameters/myproject/Detector_Temp', {
@@ -136,22 +136,22 @@ test.describe("Mdb runtime limits tests @yamcs", () => {
const detectorTreeItem = page.getByRole('treeitem', { name: /Detector_Temp/ });
// Enter edit mode for the overlay plot
- await page.click('button[title="Edit"]');
+ await page.getByLabel('Edit Object').click();
//Drag and drop the Detector_Temp telemetry endpoint into this overlay plot
const objectPane = page.locator('.c-object-view');
await detectorTreeItem.dragTo(objectPane);
// Save (exit edit mode)
- await page.locator('button[title="Save"]').click();
- await page.locator('li[title="Save and Finish Editing"]').click();
+ await page.getByRole('button', { name: 'Save' }).click();
+ await page.getByRole('listitem', { name: 'Save and Finish Editing' }).click();
// Assert that no limit lines are shown by default
await page.waitForSelector('.js-limit-area', { state: 'attached' });
expect(await page.locator('.c-plot-limit-line').count()).toBe(0);
// Enter edit mode
- await page.click('button[title="Edit"]');
+ await page.getByLabel('Edit Object').click();
// Expand the "Detector_Temp" plot series options and enable limit lines
await page.getByRole('tab', { name: 'Config' }).click();
@@ -166,8 +166,8 @@ test.describe("Mdb runtime limits tests @yamcs", () => {
.check();
// Save (exit edit mode)
- await page.locator('button[title="Save"]').click();
- await page.locator('li[title="Save and Finish Editing"]').click();
+ await page.getByRole('button', { name: 'Save' }).click();
+ await page.getByRole('listitem', { name: 'Save and Finish Editing' }).click();
//navigate away from the overlay plot
await page.goto("./", { waitUntil: "networkidle" });
diff --git a/tests/e2e/yamcs/network.e2e.spec.js b/tests/e2e/yamcs/network.e2e.spec.js
index 62a17bb5..67ce032c 100644
--- a/tests/e2e/yamcs/network.e2e.spec.js
+++ b/tests/e2e/yamcs/network.e2e.spec.js
@@ -19,117 +19,129 @@
* this source code distribution or the Licensing information page available
* at runtime from the About dialog for additional information.
*****************************************************************************/
-
/*
-Network Specific Tests
-*/
+ * Network Specific Tests for Open MCT and YAMCS connectivity.
+ * This suite verifies the network requests made by the application to ensure correct interaction with YAMCS.
+ */
-import { test, expect } from '../opensource/pluginFixtures';
-import { setFixedTimeMode } from '../opensource/appActions';
+import { test, expect } from '../opensource/pluginFixtures.js';
+import { setFixedTimeMode } from '../opensource/appActions.js';
+/**
+ * This test suite checks the network requests made by Open MCT to YAMCS.
+ */
test.describe("Quickstart network requests @yamcs", () => {
- // Collect all request events, specifically for YAMCS
+ // Keeping track of network requests during the tests.
let networkRequests = [];
let filteredRequests = [];
- test('Validate network traffic to YAMCS', async ({ page }) => {
- page.on('request', (request) => networkRequests.push(request));
- // Go to baseURL
- await page.goto("./", { waitUntil: "networkidle" });
+ // These variables hold the promises for specific network requests we expect to occur.
+ let parameterArchiveGet, batchGetStaleness, allParams, userGet, mdbOverride, mdbGet;
- const myProjectTreeItem = page.locator('.c-tree__item').filter({ hasText: 'myproject'});
- await expect(myProjectTreeItem).toBeVisible();
- const firstMyProjectTriangle = myProjectTreeItem.first().locator('span.c-disclosure-triangle');
- await firstMyProjectTriangle.click();
- const secondMyProjectTriangle = myProjectTreeItem.nth(1).locator('span.c-disclosure-triangle');
- await secondMyProjectTriangle.click();
+ test('Validate network traffic to YAMCS', async ({ page }) => {
+ // Listening for all network requests and pushing them into networkRequests array.
+ page.on('request', request => networkRequests.push(request));
- await page.waitForLoadState('networkidle');
+ // Setting up promises to wait for specific network responses.
networkRequests = [];
- await page.locator('text=CCSDS_Packet_Sequence').first().click();
- await page.waitForLoadState('networkidle');
-
- // wait for debounced requests in YAMCS Latest Telemetry Provider to finish
- await new Promise(resolve => setTimeout(resolve, 500));
+ mdbGet = page.waitForResponse('**/api/mdb/myproject/space-systems');
+ allParams = page.waitForResponse('**/api/mdb/myproject/parameters?details=yes&limit=1000');
+ userGet = page.waitForResponse('**/api/user/');
+ mdbOverride = page.waitForResponse('**/api/mdb-overrides/myproject/realtime');
+ // Testing the initial page load and verifying the presence of specific elements.
+ await page.goto("./", { waitUntil: "networkidle" });
+ await Promise.all([mdbGet, allParams, userGet, mdbOverride]);
filteredRequests = filterNonFetchRequests(networkRequests);
+ expect(filteredRequests.length).toBe(4);
- // Network requests for the composite telemetry with multiple items should be:
- // 1. batched request for latest telemetry using the bulk API
- expect(filteredRequests.length).toBe(1);
+ // I'm not sure what is going on here
+ const myProjectTreeItem = page.locator('.c-tree__item').filter({ hasText: 'myproject' });
+ await expect(myProjectTreeItem).toBeVisible();
+ await myProjectTreeItem.first().locator('span.c-disclosure-triangle').click();
+ await myProjectTreeItem.nth(1).locator('span.c-disclosure-triangle').click();
+ // More UI interactions and network request verifications.
await page.waitForLoadState('networkidle');
networkRequests = [];
- await page.locator('text=CCSDS_Packet_Length').click();
+ batchGetStaleness = page.waitForResponse('**/api/processors/myproject/realtime/parameters:batchGet');
+ await page.getByRole('treeitem', { name: 'Expand CCSDS_Packet_Sequence' }).click();
+ await batchGetStaleness;
+
await page.waitForLoadState('networkidle');
+ expect(networkRequests.length).toBe(1);
- // wait for debounced requests in YAMCS Latest Telemetry Provider to finish
- await new Promise(resolve => setTimeout(resolve, 500));
+ // Further UI interactions and network requests verification.
+ networkRequests = [];
+ parameterArchiveGet = page.waitForResponse('**/api/archive/myproject/parameters/myproject/CCSDS_Packet_Length/samples**');
+ batchGetStaleness = page.waitForResponse('**/api/processors/myproject/realtime/parameters:batchGet');
+ await page.getByRole('treeitem', { name: 'CCSDS_Packet_Length' }).click();
- filteredRequests = filterNonFetchRequests(networkRequests);
+ await Promise.all([parameterArchiveGet, batchGetStaleness]);
- // Should only be fetching:
- // 1. telemetry from parameter archive
- // 2. POST: batchGet for staleness
- expect(filteredRequests.length).toBe(2);
+ await page.waitForLoadState('networkidle');
+ expect(networkRequests.length).toBe(2);
- // Change to fixed time
+ // Simulating the change to fixed time mode and validating network requests.
+ networkRequests = [];
+ parameterArchiveGet = page.waitForResponse('**/api/archive/myproject/parameters/myproject/CCSDS_Packet_Length/samples**');
await setFixedTimeMode(page);
-
await page.waitForLoadState('networkidle');
+ await parameterArchiveGet;
+ expect(networkRequests.length).toBe(1);
+
+ // Clicking on a different telemetry item to generate new requests.
networkRequests = [];
- await page.locator('text=CCSDS_Packet_Sequence').first().click();
- await page.waitForLoadState('networkidle');
+ let groupFlagsGet = page.waitForResponse('**/api/archive/myproject/parameters/myproject/CCSDS_Packet_Sequence.GroupFlags**');
+ let countGet = page.waitForResponse('**/api/archive/myproject/parameters/myproject/CCSDS_Packet_Sequence.Count**');
+ batchGetStaleness = page.waitForResponse('**/api/processors/myproject/realtime/parameters:batchGet');
- // wait for debounced requests in YAMCS Latest Telemetry Provider to finish
- await new Promise(resolve => setTimeout(resolve, 500));
+ await page.getByRole('treeitem', { name: 'Expand CCSDS_Packet_Sequence' }).click();
+ await page.waitForLoadState('networkidle');
- filteredRequests = filterNonFetchRequests(networkRequests);
+ await Promise.all([groupFlagsGet, countGet, batchGetStaleness]);
- // Should fetch from parameter archive, so:
- // 1. GET for first telemetry item from parameter archive
- // 2. GET for second telemetry item from parameter archive
- // 3. POST: batchGet for staleness
- expect(filteredRequests.length).toBe(3);
+ expect(networkRequests.length).toBe(3);
- await page.waitForLoadState('networkidle');
+ // Clicking on the telemetry item in Fixed Time mode to generate two requests.
networkRequests = [];
- await page.locator('text=CCSDS_Packet_Length').first().click();
+ parameterArchiveGet = page.waitForResponse('**/api/archive/myproject/parameters/myproject/CCSDS_Packet_Length/samples**');
+ batchGetStaleness = page.waitForResponse('**/api/processors/myproject/realtime/parameters:batchGet');
+ await page.getByRole('treeitem', { name: 'CCSDS_Packet_Length' }).click();
await page.waitForLoadState('networkidle');
- // wait for debounced requests in YAMCS Latest Telemetry Provider to finish
- await new Promise(resolve => setTimeout(resolve, 500));
- filteredRequests = filterNonFetchRequests(networkRequests);
- // Should only be fetching telemetry from parameter archive,
- // with no further request for limits should be made.
- // 1. GET for telemetry item from parameter archive
- // 2. POST: batchGet for staleness
- expect(filteredRequests.length).toBe(2);
+ await Promise.all([parameterArchiveGet, batchGetStaleness]);
+
+ // Waiting for debounced requests in YAMCS Latest Telemetry Provider to finish.
+ expect(networkRequests.length).toBe(2);
+ // Simulating a page refresh to generate a sequence of network requests.
networkRequests = [];
- await page.reload();
- await page.waitForLoadState('networkidle');
+ userGet = page.waitForResponse('**/api/user/');
+ allParams = page.waitForResponse('**/api/mdb/myproject/parameters?details=yes&limit=1000');
+ mdbOverride = page.waitForResponse('**/api/mdb-overrides/myproject/realtime');
+ parameterArchiveGet = page.waitForResponse('**/api/archive/myproject/parameters/myproject/CCSDS_Packet_Length/samples**');
+ batchGetStaleness = page.waitForResponse('**/api/processors/myproject/realtime/parameters:batchGet');
+ mdbOverride = page.waitForResponse('**/api/mdb-overrides/myproject/realtime');
- // wait for debounced requests in YAMCS Latest Telemetry Provider to finish
- await new Promise(resolve => setTimeout(resolve, 500));
+ await page.reload({ waitUntil: 'networkidle' });
+ await Promise.all([allParams, userGet, mdbOverride, parameterArchiveGet, batchGetStaleness, mdbOverride]);
- // Should be fetching:
- // 1. user api
- // 2. space systems
- // 3. parameter dictionary
- // 4. specific parameter telemetry for CCSDS_Packet_Length
- // 5. POST: batchGet for staleness
- // 6. GET for telemetry item mdb overrides
+ // Waiting for debounced requests in YAMCS Latest Telemetry Provider to finish.
filteredRequests = filterNonFetchRequests(networkRequests);
expect(filteredRequests.length).toBe(6);
+ // Removing the 'request' event listener to prevent potential memory leaks.
+ page.removeListener('request', request => networkRequests.push(request));
});
- // Try to reduce indeterminism of browser requests by only returning fetch requests.
- // Filter out preflight CORS, fetching stylesheets, page icons, etc. that can occur during tests
+ /**
+ * Filters out non-fetch requests from the given array of network requests.
+ * This includes preflight CORS, fetching stylesheets, page icons, etc.
+ * @param {Array} requests - Array of network requests to filter.
+ * @returns {Array} Filtered network requests.
+ */
function filterNonFetchRequests(requests) {
- return requests.filter(request => {
- return (request.resourceType() === 'fetch');
- });
+ return requests.filter(request => request.resourceType() === 'fetch');
}
});
diff --git a/tests/e2e/yamcs/quickstartSmoke.e2e.spec.js b/tests/e2e/yamcs/quickstartSmoke.e2e.spec.js
index f7d9056d..611cef9c 100644
--- a/tests/e2e/yamcs/quickstartSmoke.e2e.spec.js
+++ b/tests/e2e/yamcs/quickstartSmoke.e2e.spec.js
@@ -33,7 +33,7 @@ comfortable running this test during a live mission?" Avoid creating or deleting
Make no assumptions about the order that elements appear in the DOM.
*/
-import { test, expect } from '../opensource/baseFixtures';
+import { test, expect } from '../opensource/baseFixtures.js';
test.describe("Quickstart smoke tests @yamcs", () => {
test('Verify that the create button appears and that the Folder Domain Object is available for selection', async ({ page }) => {
@@ -41,7 +41,7 @@ test.describe("Quickstart smoke tests @yamcs", () => {
await page.goto('./', { waitUntil: 'networkidle' });
//Click the Create button
- await page.click('button:has-text("Create")');
+ await page.getByRole('button', { name: 'Create' }).click();
// Verify that Create Folder appears in the dropdown
await expect(page.locator(':nth-match(:text("Folder"), 2)')).toBeEnabled();
diff --git a/tests/e2e/yamcs/search.e2e.spec.js b/tests/e2e/yamcs/search.e2e.spec.js
index e24fdf29..72984e40 100644
--- a/tests/e2e/yamcs/search.e2e.spec.js
+++ b/tests/e2e/yamcs/search.e2e.spec.js
@@ -24,7 +24,7 @@
Search Specific Tests
*/
-import { test, expect } from '../opensource/pluginFixtures';
+import { test, expect } from '../opensource/pluginFixtures.js';
test.describe("Quickstart search tests @yamcs", () => {
test('Validate aggregate in search result', async ({ page }) => {
diff --git a/tests/git-opensource-tests.sh b/tests/git-opensource-tests.sh
index 1f99b818..419bbbf5 100644
--- a/tests/git-opensource-tests.sh
+++ b/tests/git-opensource-tests.sh
@@ -36,6 +36,6 @@ mv opensource/e2e/appActions.js ./opensource
# Move subfolders
mv opensource/e2e/*/ ./opensource
# Move eslint config
-mv opensource/e2e/.eslintrc.cjs ./opensource
+mv opensource/e2e/.eslintrc.*js ./opensource
# Cleanup
rm -rf opensource/e2e