diff --git a/openc3-cosmos-init/plugins/packages/openc3-cosmos-tool-cmdsender/src/tools/CommandSender/CommandSender.vue b/openc3-cosmos-init/plugins/packages/openc3-cosmos-tool-cmdsender/src/tools/CommandSender/CommandSender.vue index abf6b29064..fefce5ea3b 100644 --- a/openc3-cosmos-init/plugins/packages/openc3-cosmos-tool-cmdsender/src/tools/CommandSender/CommandSender.vue +++ b/openc3-cosmos-init/plugins/packages/openc3-cosmos-tool-cmdsender/src/tools/CommandSender/CommandSender.vue @@ -132,15 +132,17 @@ - Warning: Command {{ hazardousCommand }} is Hazardous. Send? -
Description: {{ commandDescription }}
+ Warning: {{ hazardousMessage }}. Send? +
+ Description: {{ hazardousDialogDescription }} +
mdi-alert - Hazardous: {{ hazardousDescription }} + Hazardous: {{ hazardousDialogHazardousDescription }}
@@ -253,6 +255,9 @@ export default { status: '', history: '', hazardousCommand: '', + commandIsHazardous: false, + hazardousDialogDescription: '', + hazardousDialogHazardousDescription: '', displaySendHazardous: false, displayErrorDialog: false, displaySendRaw: false, @@ -270,6 +275,27 @@ export default { } }, computed: { + // Modes that make every command hazardous, e.g. 'range checks are ignored' + hazardousModes() { + const modes = [] + if (this.ignoreRangeChecks) { + modes.push('range checks are ignored') + } + if (this.cmdRaw) { + modes.push('parameter conversions are disabled') + } + return modes.join(' and ') + }, + hazardousMessage() { + const command = `Command ${this.hazardousCommand}` + if (!this.hazardousModes) { + return `${command} is Hazardous` + } else if (this.commandIsHazardous) { + return `${command} is Hazardous and ${this.hazardousModes}` + } else { + return `${command} is considered Hazardous because ${this.hazardousModes}` + } + }, readOnlyUser: function () { const roles = OpenC3Auth.userroles() || [] return roles.length > 0 && roles.every((role) => role === 'viewer') @@ -555,17 +581,33 @@ export default { this.sendDisabled = true this.api.get_cmd_hazardous(targetName, commandName, paramList).then( (hazardous) => { - if (hazardous) { - // If it was sent from history it's all in targetName + // Ignoring range checks allows arbitrary values (e.g. manually + // entered states) and disabling conversions bypasses any that make + // a command safe so treat every command as hazardous + if (hazardous || this.hazardousModes) { + this.commandIsHazardous = hazardous + // If it was sent from history it's all in targetName and may + // differ from the selected command so look up its descriptions if (commandName === undefined) { this.hazardousCommand = targetName .split(' ') .slice(0, 2) .join(' ') + this.api.get_cmd(...this.hazardousCommand.split(' ')).then( + (command) => { + this.showHazardousDialog(command) + }, + () => { + this.showHazardousDialog(null) + }, + ) } else { this.hazardousCommand = `${targetName} ${commandName}` + this.showHazardousDialog({ + description: this.commandDescription, + hazardous_description: this.hazardousDescription, + }) } - this.displaySendHazardous = true } else { this.executeCmd(cmdInfo, false) } @@ -576,6 +618,13 @@ export default { ) }, + showHazardousDialog(command) { + this.hazardousDialogDescription = command?.description || '' + this.hazardousDialogHazardousDescription = + command?.hazardous_description || '' + this.displaySendHazardous = true + }, + sendHazardousCmd() { this.displaySendHazardous = false this.executeCmd( diff --git a/openc3-cosmos-init/plugins/packages/openc3-vue-common/src/components/CommandEditor.vue b/openc3-cosmos-init/plugins/packages/openc3-vue-common/src/components/CommandEditor.vue index 1c5192c064..0f5d4f98b6 100644 --- a/openc3-cosmos-init/plugins/packages/openc3-vue-common/src/components/CommandEditor.vue +++ b/openc3-cosmos-init/plugins/packages/openc3-vue-common/src/components/CommandEditor.vue @@ -18,7 +18,7 @@ ref="targetPacketChooser" :initial-target-name="targetName" :initial-packet-name="commandName" - :disabled="sendDisabled" + :disabled="sendDisabled || commandLoading" :disable-button-text="readOnly ? 'Viewers cannot send commands' : null" :button-text="showCommandButton ? 'Send' : null" :show-queue-select="showQueueSelect" @@ -176,6 +176,9 @@ export default { targetName: '', commandName: '', commandDescription: '', + // Keep Send disabled until the selected command's parameters and + // descriptions are loaded so we never send with stale values + commandLoading: false, ignoredParams: [], computedRows: [], reservedItemNames: [ @@ -250,6 +253,7 @@ export default { this.computedRows = [] this.hazardousParameters = {} if (this.targetName && this.commandName) { + this.commandLoading = true this.api .get_target(this.targetName) .then( @@ -263,6 +267,7 @@ export default { ) .then( (command) => { + this.commandLoading = false if (command) { // Identify length fields that are auto-managed by variable_bit_size arrays // These should be shown but disabled - they will be set when the array is written @@ -343,6 +348,7 @@ export default { } }, (error) => { + this.commandLoading = false console.error('Error getting command parameters:', error) this.$emit('command-loaded', null) }, diff --git a/openc3-cosmos-init/plugins/packages/openc3-vue-common/src/components/CommandParameterEditor.vue b/openc3-cosmos-init/plugins/packages/openc3-vue-common/src/components/CommandParameterEditor.vue index e0c565324b..a577cff747 100644 --- a/openc3-cosmos-init/plugins/packages/openc3-vue-common/src/components/CommandParameterEditor.vue +++ b/openc3-cosmos-init/plugins/packages/openc3-vue-common/src/components/CommandParameterEditor.vue @@ -145,6 +145,10 @@ export default { if (!this.states) { return false } + // Manual entry requires ignoring range checks which is always hazardous + if (this.manuallyEntered) { + return true + } return ( Object.entries(this.states) .find(([label, state]) => state.value === this.modelValue) @@ -195,7 +199,8 @@ export default { .container { padding: 0px; } -.hazardous :deep(.v-select__selection-text) { +/* Extra .v-select specificity beats the themed override in _overrides.scss */ +.v-select.hazardous :deep(.v-select__selection-text) { color: rgb(255, 220, 0) !important; } diff --git a/playwright/tests/command-sender.p.spec.ts b/playwright/tests/command-sender.p.spec.ts index 57a9548938..e0c9d27456 100644 --- a/playwright/tests/command-sender.p.spec.ts +++ b/playwright/tests/command-sender.p.spec.ts @@ -52,11 +52,32 @@ async function checkValue(page, param, value) { ).toHaveValue(value) } +// Helper function to toggle a Command Sender mode menu option +async function toggleMode(page, mode) { + await page.locator('[data-test=command-sender-mode]').click() + await page.getByText(mode).click() + await page.locator('[data-test=command-sender-mode]').click() +} + +// Helper function to send the command and confirm the hazardous dialog, +// optionally checking the dialog's warning message first +async function sendHazardousCmd(page, warning?: string) { + await page.locator('[data-test="select-send"]').click() + if (warning) { + await expect(page.getByRole('dialog')).toContainText(warning) + } + await page.getByRole('dialog').getByRole('button', { name: 'Send' }).click() +} + +// Helper function to check the command was sent and added to the history +async function checkSent(page, cmd) { + await expect(page.locator('main')).toContainText(`${cmd} sent`) + await checkHistory(page, cmd) +} + // Helper function to check command history async function checkHistory(page, value) { - await page - .locator('[data-test=sender-history] div') - .filter({ hasText: value }) + await expect(page.locator('[data-test=sender-history]')).toContainText(value) } // @@ -120,6 +141,9 @@ test('warns for hazardous commands', async ({ page, utils }) => { 'Hazardous: Clearing counters may lose valuable information.', ) await page.locator('[data-test="select-send"]').click() + await expect(page.getByRole('dialog')).toContainText( + 'Warning: Command INST CLEAR is Hazardous. Send?', + ) // The hazardous confirmation dialog shows both the command description and // the hazardous description (see issue 3472) await expect(page.getByRole('dialog')).toContainText( @@ -130,44 +154,36 @@ test('warns for hazardous commands', async ({ page, utils }) => { ) await page.getByRole('button', { name: 'Cancel' }).click() await expect(page.locator('main')).toContainText('Hazardous command not sent') - await page.locator('[data-test="select-send"]').click() - await page.getByRole('dialog').getByRole('button', { name: 'Send' }).click() - await expect(page.locator('main')).toContainText('cmd("INST CLEAR") sent') - await checkHistory(page, 'cmd("INST CLEAR")') + await sendHazardousCmd(page) + await checkSent(page, 'cmd("INST CLEAR")') // Disable range checks to confirm history output - await page.locator('[data-test=command-sender-mode]').click() - await page.locator('text=Ignore Range Checks').click() - await page.locator('[data-test=command-sender-mode]').click() + await toggleMode(page, 'Ignore Range Checks') - await page.locator('[data-test="select-send"]').click() - await page.getByRole('dialog').getByRole('button', { name: 'Send' }).click() - await expect(page.locator('main')).toContainText( - 'cmd_no_range_check("INST CLEAR") sent', + // A hazardous command notes that range checks are also ignored + await sendHazardousCmd( + page, + 'Warning: Command INST CLEAR is Hazardous and range checks are ignored. Send?', ) - await checkHistory(page, 'cmd_no_range_check("INST CLEAR")') + await checkSent(page, 'cmd_no_range_check("INST CLEAR")') // Disable parameter conversions to confirm history output - await page.locator('[data-test=command-sender-mode]').click() - await page.locator('text=Disable Parameter').click() - await page.locator('[data-test=command-sender-mode]').click() + await toggleMode(page, 'Disable Parameter') - await page.locator('[data-test="select-send"]').click() - await page.getByRole('dialog').getByRole('button', { name: 'Send' }).click() - await expect(page.locator('main')).toContainText( - 'cmd_raw_no_range_check("INST CLEAR") sent', + await sendHazardousCmd( + page, + 'Warning: Command INST CLEAR is Hazardous and range checks are ignored and parameter conversions are disabled. Send?', ) - await checkHistory(page, 'cmd_raw_no_range_check("INST CLEAR")') + await checkSent(page, 'cmd_raw_no_range_check("INST CLEAR")') // Enable range checks to confirm history output - await page.locator('[data-test=command-sender-mode]').click() - await page.locator('text=Ignore Range Checks').click() - await page.locator('[data-test=command-sender-mode]').click() + await toggleMode(page, 'Ignore Range Checks') - await page.locator('[data-test="select-send"]').click() - await page.getByRole('dialog').getByRole('button', { name: 'Send' }).click() - await expect(page.locator('main')).toContainText('cmd_raw("INST CLEAR") sent') - await checkHistory(page, 'cmd_raw("INST CLEAR")') + await sendHazardousCmd( + page, + 'Warning: Command INST CLEAR is Hazardous and parameter conversions are disabled. Send?', + ) + await checkSent(page, 'cmd_raw("INST CLEAR")') }) test('warns for required parameters', async ({ page, utils }) => { @@ -190,16 +206,18 @@ test('warns for hazardous parameters', async ({ page, utils }) => { 'Starts a collect on the INST target', ) await selectValue(page, 'TYPE', 'SPECIAL') + // Hazardous states are highlighted in yellow + await expect( + page + .locator('tr:has(td:text-is("TYPE"))') + .locator('.v-select__selection-text'), + ).toHaveCSS('color', 'rgb(255, 220, 0)') await page.locator('[data-test="select-send"]').click() await page.getByRole('button', { name: 'Cancel' }).click() await expect(page.locator('main')).toContainText('Hazardous command not sent') - await page.locator('[data-test="select-send"]').click() - await page.getByRole('dialog').getByRole('button', { name: 'Send' }).click() + await sendHazardousCmd(page) // Ensure the state is used, e.g. 'SPECIAL' and not the value - await expect(page.locator('main')).toContainText( - 'cmd("INST COLLECT with TYPE \'SPECIAL\', DURATION 1, OPCODE 171, TEMP 0") sent', - ) - await checkHistory( + await checkSent( page, 'cmd("INST COLLECT with TYPE \'SPECIAL\', DURATION 1, OPCODE 171, TEMP 0")', ) @@ -217,10 +235,7 @@ test('handles float values and scientific notation', async ({ await setValue(page, 'FLOAT32', '123.456') await setValue(page, 'FLOAT64', '12e3') await page.locator('[data-test="select-send"]').click() - await expect(page.locator('main')).toContainText( - 'cmd("INST FLTCMD with FLOAT32 123.456, FLOAT64 12000") sent', - ) - await checkHistory( + await checkSent( page, 'cmd("INST FLTCMD with FLOAT32 123.456, FLOAT64 12000")', ) @@ -239,26 +254,18 @@ test('handles NaN and Infinite values', async ({ page, utils }) => { await expect(page.locator('.v-dialog')).toContainText('not in valid range') await page.locator('button:has-text("Ok")').click() // Disable range checks - await page.locator('[data-test=command-sender-mode]').click() - await page.locator('text=Ignore Range Checks').click() - await page.locator('[data-test=command-sender-mode]').click() - await page.locator('[data-test="select-send"]').click() + await toggleMode(page, 'Ignore Range Checks') + await sendHazardousCmd(page) - await expect(page.locator('main')).toContainText( - 'cmd_no_range_check("INST FLTCMD with FLOAT32 NaN, FLOAT64 NaN") sent', - ) - await checkHistory( + await checkSent( page, 'cmd_no_range_check("INST FLTCMD with FLOAT32 NaN, FLOAT64 NaN")', ) await setValue(page, 'FLOAT32', 'INFINITY') await setValue(page, 'FLOAT64', '-infinity') - await page.locator('[data-test="select-send"]').click() - await expect(page.locator('main')).toContainText( - 'cmd_no_range_check("INST FLTCMD with FLOAT32 Infinity, FLOAT64 -Infinity") sent', - ) - await checkHistory( + await sendHazardousCmd(page) + await checkSent( page, 'cmd_no_range_check("INST FLTCMD with FLOAT32 Infinity, FLOAT64 -Infinity")', ) @@ -273,12 +280,9 @@ test('handles big integers', async ({ page, utils }) => { let value = '18446744073709551615' await setValue(page, 'BIGINT', value) await page.locator('[data-test="select-send"]').click() - await expect(page.locator('main')).toContainText( - `cmd("INST SETPARAMS with VALUE1 1, VALUE2 1, VALUE3 1, VALUE4 1, VALUE5 1, BIGINT ${value}") sent.`, - ) - await checkHistory( + await checkSent( page, - `cmd("INST SETPARAMS with VALUE1 1, VALUE2 1, VALUE3 1, VALUE4 1, VALUE5 1, BIGINT ${value}") sent.`, + `cmd("INST SETPARAMS with VALUE1 1, VALUE2 1, VALUE3 1, VALUE4 1, VALUE5 1, BIGINT ${value}")`, ) // Check the screen await expect( @@ -292,12 +296,9 @@ test('handles big integers', async ({ page, utils }) => { value = '1740684371613049856' await setValue(page, 'BIGINT', value) await page.locator('[data-test="select-send"]').click() - await expect(page.locator('main')).toContainText( - `cmd("INST SETPARAMS with VALUE1 1, VALUE2 1, VALUE3 1, VALUE4 1, VALUE5 1, BIGINT ${value}") sent.`, - ) - await checkHistory( + await checkSent( page, - `cmd("INST SETPARAMS with VALUE1 1, VALUE2 1, VALUE3 1, VALUE4 1, VALUE5 1, BIGINT ${value}") sent.`, + `cmd("INST SETPARAMS with VALUE1 1, VALUE2 1, VALUE3 1, VALUE4 1, VALUE5 1, BIGINT ${value}")`, ) // Check the screen await expect( @@ -310,12 +311,9 @@ test('handles big integers', async ({ page, utils }) => { value = '0' // Set the BIGINT value to 0 for later tests await setValue(page, 'BIGINT', value) await page.locator('[data-test="select-send"]').click() - await expect(page.locator('main')).toContainText( - `cmd("INST SETPARAMS with VALUE1 1, VALUE2 1, VALUE3 1, VALUE4 1, VALUE5 1, BIGINT ${value}") sent.`, - ) - await checkHistory( + await checkSent( page, - `cmd("INST SETPARAMS with VALUE1 1, VALUE2 1, VALUE3 1, VALUE4 1, VALUE5 1, BIGINT ${value}") sent.`, + `cmd("INST SETPARAMS with VALUE1 1, VALUE2 1, VALUE3 1, VALUE4 1, VALUE5 1, BIGINT ${value}")`, ) // Check the screen await expect( @@ -338,13 +336,7 @@ test('handles array values', async ({ page, utils }) => { await page.locator('button:has-text("Ok")').click() await setValue(page, 'ARRAY', '[1,2,3,4]') await page.locator('[data-test="select-send"]').click() - await expect(page.locator('main')).toContainText( - 'cmd("INST ARYCMD with ARRAY [ 1, 2, 3, 4 ], CRC 0") sent', - ) - await checkHistory( - page, - 'cmd("INST ARYCMD with ARRAY [ 1, 2, 3, 4 ], CRC 0")', - ) + await checkSent(page, 'cmd("INST ARYCMD with ARRAY [ 1, 2, 3, 4 ], CRC 0")') }) test('handles string values', async ({ page, utils }) => { @@ -391,10 +383,8 @@ test('executes commands from history', async ({ page, utils }) => { await expect(page.locator('main')).toContainText( 'Clears counters on the INST instrument', ) - await page.locator('[data-test="select-send"]').click() - await page.getByRole('dialog').getByRole('button', { name: 'Send' }).click() - await expect(page.locator('main')).toContainText('cmd("INST CLEAR") sent') - await checkHistory(page, 'cmd("INST CLEAR")') + await sendHazardousCmd(page) + await checkSent(page, 'cmd("INST CLEAR")') // Re-execute the command from the history await page.locator('[data-test=sender-history]').click() await page.locator('[data-test=sender-history]').press('ArrowUp') @@ -491,8 +481,7 @@ test('send vs history', async ({ page, utils }) => { 'Aborts a collect on the INST instrument', ) await page.locator('[data-test="select-send"]').click() - await expect(page.locator('main')).toContainText('cmd("INST ABORT") sent') - await checkHistory(page, 'cmd("INST ABORT")') + await checkSent(page, 'cmd("INST ABORT")') // Send a different command: INST SETPARAMS await utils.selectTargetPacketItem('INST', 'SETPARAMS') await expect(page.locator('main')).toContainText('Sets numbered parameters') @@ -519,17 +508,14 @@ test('hazardous commands from history', async ({ page, utils }) => { await expect(page.locator('main')).toContainText( 'Clears counters on the INST instrument', ) - await page.locator('[data-test="select-send"]').click() - await page.getByRole('dialog').getByRole('button', { name: 'Send' }).click() - await expect(page.locator('main')).toContainText('cmd("INST CLEAR") sent') - await checkHistory(page, 'cmd("INST CLEAR")') + await sendHazardousCmd(page) + await checkSent(page, 'cmd("INST CLEAR")') // Send a different command: INST ASCIICMD await utils.selectTargetPacketItem('INST', 'ASCIICMD') await expect(page.locator('main')).toContainText('Enumerated ASCII command') await selectValue(page, 'STRING', 'ARM LASER') await checkValue(page, 'STRING', 'ARM LASER') - await page.locator('[data-test="select-send"]').click() - await page.getByRole('dialog').getByRole('button', { name: 'Send' }).click() + await sendHazardousCmd(page) await expect(page.locator('main')).toContainText( "cmd(\"INST ASCIICMD with STRING 'ARM LASER', BINARY 0xDEADBEEF, ASCII '0xDEADBEEF'\")", ) @@ -537,6 +523,16 @@ test('hazardous commands from history', async ({ page, utils }) => { await page.locator('[data-test=sender-history]').click() await page.locator('[data-test=sender-history]').press('ArrowDown') await page.locator('[data-test=sender-history]').press('Enter') + // The dialog describes the history command, not the selected INST ASCIICMD + await expect(page.getByRole('dialog')).toContainText( + 'Warning: Command INST CLEAR is Hazardous. Send?', + ) + await expect(page.getByRole('dialog')).toContainText( + 'Description: Clears counters on the INST instrument', + ) + await expect( + page.getByRole('dialog').locator('[data-test=hazardous-description]'), + ).toContainText('Clearing counters may lose valuable information.') await page.getByRole('dialog').getByRole('button', { name: 'Send' }).click() await expect(page.locator('main')).toContainText('cmd("INST CLEAR") sent') await page.locator('[data-test=sender-history]').click() @@ -570,22 +566,19 @@ test('ignores normal range checks', async ({ page, utils }) => { ) await page.locator('button:has-text("Ok")').click() // Disable range checks - await page.locator('[data-test=command-sender-mode]').click() - await page.getByText('Ignore Range Checks').click() - await page.locator('[data-test=command-sender-mode]').click() - await page.locator('[data-test="select-send"]').click() - await expect(page.locator('main')).toContainText( - 'cmd_no_range_check("INST COLLECT with TYPE \'NORMAL\', DURATION 1, OPCODE 171, TEMP 100") sent', + await toggleMode(page, 'Ignore Range Checks') + // Ignoring range checks makes every command hazardous + await sendHazardousCmd( + page, + 'Warning: Command INST COLLECT is considered Hazardous because range checks are ignored. Send?', ) - await checkHistory( + await checkSent( page, 'cmd_no_range_check("INST COLLECT with TYPE \'NORMAL\', DURATION 1, OPCODE 171, TEMP 100")', ) // Enable range checks - await page.locator('[data-test=command-sender-mode]').click() - await page.getByText('Ignore Range Checks').click() - await page.locator('[data-test=command-sender-mode]').click() + await toggleMode(page, 'Ignore Range Checks') await utils.selectTargetPacketItem('EXAMPLE', 'START') await expect(page.locator('main')).toContainText( 'Starts something on the example target', @@ -613,26 +606,89 @@ test('ignores hazardous range checks', async ({ page, utils }) => { await utils.selectTargetPacketItem('INST', 'COLLECT') await selectValue(page, 'TYPE', 'SPECIAL') // Special is hazardous await setValue(page, 'TEMP', '100') - await page.locator('[data-test="select-send"]').click() - await page.getByRole('dialog').getByRole('button', { name: 'Send' }).click() // Hazardous confirm + await sendHazardousCmd(page) // Dialog should pop up with error await expect(page.locator('.v-dialog:has-text("Error")')).toContainText( 'not in valid range', ) await page.locator('button:has-text("Ok")').click() // Disable range checks - await page.locator('[data-test=command-sender-mode]').click() - await page.locator('text=Ignore Range Checks').click() - await page.locator('[data-test=command-sender-mode]').click() + await toggleMode(page, 'Ignore Range Checks') + // The dialog also notes that range checks are ignored + await sendHazardousCmd( + page, + 'Warning: Command INST COLLECT is Hazardous and range checks are ignored. Send?', + ) + await checkSent( + page, + 'cmd_no_range_check("INST COLLECT with TYPE \'SPECIAL\', DURATION 1, OPCODE 171, TEMP 100")', + ) +}) + +test('confirms commands when ignoring range checks', async ({ + page, + utils, +}) => { + await page.locator('[data-test="clear-history"]').click() + await utils.selectTargetPacketItem('INST', 'COLLECT') + await selectValue(page, 'TYPE', 'NORMAL') + await toggleMode(page, 'Ignore Range Checks') + + // Cancelling a command that's only hazardous due to ignored range checks await page.locator('[data-test="select-send"]').click() - await page.getByRole('dialog').getByRole('button', { name: 'Send' }).click() // Hazardous confirm + await expect(page.getByRole('dialog')).toContainText( + 'Warning: Command INST COLLECT is considered Hazardous because range checks are ignored. Send?', + ) + await page.getByRole('dialog').getByRole('button', { name: 'Cancel' }).click() + await expect(page.locator('main')).toContainText('Hazardous command not sent') + await expect(page.locator('[data-test=sender-history]')).not.toContainText( + 'INST COLLECT', + ) + + // Send it so it's in the history + await sendHazardousCmd(page) await expect(page.locator('main')).toContainText( - 'cmd_no_range_check("INST COLLECT with TYPE \'SPECIAL\', DURATION 1, OPCODE 171, TEMP 100") sent', + 'cmd_no_range_check("INST COLLECT with TYPE \'NORMAL\', DURATION 1, OPCODE 171, TEMP 0") sent', ) - await checkHistory( + + // Resending from history still requires confirmation and names the command. + // Select a different command to ensure the dialog describes the history + // command rather than the selected one. + await utils.selectTargetPacketItem('INST', 'CLEAR') + await expect(page.locator('main')).toContainText( + 'Clears counters on the INST instrument', + ) + await page.locator('[data-test=sender-history]').click() + await page.locator('[data-test=sender-history]').press('Enter') + await expect(page.getByRole('dialog')).toContainText( + 'Warning: Command INST COLLECT is considered Hazardous because range checks are ignored. Send?', + ) + await expect(page.getByRole('dialog')).toContainText( + 'Description: Starts a collect on the INST target', + ) + await expect( + page.getByRole('dialog').locator('[data-test=hazardous-description]'), + ).toHaveCount(0) + await page.getByRole('dialog').getByRole('button', { name: 'Send' }).click() + await expect(page.locator('main')).toContainText( + 'cmd_no_range_check("INST COLLECT with TYPE \'NORMAL\', DURATION 1, OPCODE 171, TEMP 0") sent', + ) +}) + +test('confirms commands when disabling parameter conversions', async ({ + page, + utils, +}) => { + await page.locator('[data-test="clear-history"]').click() + await utils.selectTargetPacketItem('INST', 'COLLECT') + await selectValue(page, 'TYPE', 'NORMAL') + await toggleMode(page, 'Disable Parameter') + + await sendHazardousCmd( page, - 'cmd_no_range_check("INST COLLECT with TYPE \'SPECIAL\', DURATION 1, OPCODE 171, TEMP 100")', + 'Warning: Command INST COLLECT is considered Hazardous because parameter conversions are disabled. Send?', ) + await expect(page.locator('main')).toContainText('cmd_raw("INST COLLECT') }) // @@ -737,14 +793,17 @@ test('sends manually entered state values', async ({ page, utils }) => { ) // Enable Ignore Range Checks to unlock manual entry - await page.locator('[data-test=command-sender-mode]').click() - await page.getByText('Ignore Range Checks').click() - await page.locator('[data-test=command-sender-mode]').click() + await toggleMode(page, 'Ignore Range Checks') // Select MANUALLY_ENTERED from the TYPE state dropdown let row = page.locator('tr:has(td:text-is("TYPE"))') await row.locator('[data-test=cmd-param-select]').click({ force: true }) await page.getByRole('option', { name: 'MANUALLY_ENTERED' }).click() + // Manual entry is hazardous so it's highlighted in yellow + await expect(row.locator('.v-select__selection-text')).toHaveCSS( + 'color', + 'rgb(255, 220, 0)', + ) // Enter a raw numeric value in the manual entry text field await expect( @@ -753,20 +812,18 @@ test('sends manually entered state values', async ({ page, utils }) => { await row.locator('[data-test=cmd-param-value] input').first().fill('0') await row.locator('[data-test=cmd-param-value] input').first().press('Enter') - // Send the command - await page.locator('[data-test="select-send"]').click() - await expect(page.locator('main')).toContainText( - 'cmd_no_range_check("INST COLLECT with TYPE 0, DURATION 1, OPCODE 171, TEMP 0") sent', + // Ignoring range checks makes every command hazardous + await sendHazardousCmd( + page, + 'Warning: Command INST COLLECT is considered Hazardous because range checks are ignored. Send?', ) - await checkHistory( + await checkSent( page, 'cmd_no_range_check("INST COLLECT with TYPE 0, DURATION 1, OPCODE 171, TEMP 0")', ) // Disable Ignore Range Checks and verify MANUALLY_ENTERED option is gone - await page.locator('[data-test=command-sender-mode]').click() - await page.getByText('Ignore Range Checks').click() - await page.locator('[data-test=command-sender-mode]').click() + await toggleMode(page, 'Ignore Range Checks') // The TYPE dropdown should no longer have MANUALLY_ENTERED await row.locator('[data-test=cmd-param-select]').click({ force: true }) diff --git a/playwright/tests/command-sender.s.spec.ts b/playwright/tests/command-sender.s.spec.ts index 9eb0693f4d..3380da7978 100644 --- a/playwright/tests/command-sender.s.spec.ts +++ b/playwright/tests/command-sender.s.spec.ts @@ -124,6 +124,7 @@ test('disable parameter conversions', async ({ page, utils }) => { await expect(page.locator('main')).toContainText('Sets numbered parameters') await expect(page.locator('main')).toContainText('Value 1 setting') // Ensures the params are rendered await page.locator('[data-test="select-send"]').click() + await page.getByRole('dialog').getByRole('button', { name: 'Send' }).click() // Hazardous confirm await expect(page.locator('main')).toContainText( 'cmd_raw("INST SETPARAMS with VALUE1 1, VALUE2 1, VALUE3 1, VALUE4 1, VALUE5 1, BIGINT 0") sent', ) @@ -137,6 +138,7 @@ test('disable parameter conversions', async ({ page, utils }) => { await page.locator('text=Ignore Range Checks').click() await page.locator('[data-test=command-sender-mode]').click() await page.locator('[data-test="select-send"]').click() + await page.getByRole('dialog').getByRole('button', { name: 'Send' }).click() // Hazardous confirm await expect(page.locator('main')).toContainText( 'cmd_raw_no_range_check("INST SETPARAMS with VALUE1 1, VALUE2 1, VALUE3 1, VALUE4 1, VALUE5 1, BIGINT 0") sent', )