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 e2fd60a164..fb0954dd90 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 @@ -265,8 +265,11 @@ export default { if (parameter.format_string && parameter.default) { val = sprintf(parameter.format_string, parameter.default) } - if (Object.prototype.toString.call(val).slice(8, -1) === 'Object') { - val = JSON.stringify(val).replace(/\\n/g, '') + if ( + Object.prototype.toString.call(val).slice(8, -1) === + 'Object' + ) { + val = this.convertToString(val) } let range = 'N/A' if ( @@ -275,12 +278,10 @@ export default { ) { if (parameter.data_type === 'FLOAT') { if (parameter.minimum < -1e6) { - parameter.minimum = - parameter.minimum.toExponential(3) + parameter.minimum = parameter.minimum.toExponential(3) } if (parameter.maximum > 1e6) { - parameter.maximum = - parameter.maximum.toExponential(3) + parameter.maximum = parameter.maximum.toExponential(3) } } range = `${parameter.minimum}..${parameter.maximum}` diff --git a/openc3-cosmos-init/plugins/packages/openc3-vue-common/src/util/cmdUtilities.js b/openc3-cosmos-init/plugins/packages/openc3-vue-common/src/util/cmdUtilities.js index efe207ef2b..5790c00b4f 100644 --- a/openc3-cosmos-init/plugins/packages/openc3-vue-common/src/util/cmdUtilities.js +++ b/openc3-cosmos-init/plugins/packages/openc3-vue-common/src/util/cmdUtilities.js @@ -97,6 +97,9 @@ export default { } } else if (value.json_class === 'Float' && value.raw) { returnValue = value.raw + } else { + // For other objects, use JSON.stringify as a fallback + returnValue = JSON.stringify(value).replace(/\\n/g, '') } } else { returnValue = String(value) diff --git a/playwright/package.json b/playwright/package.json index 4d90082897..06c61d9530 100644 --- a/playwright/package.json +++ b/playwright/package.json @@ -5,7 +5,7 @@ "description": "OpenC3 integration testing", "scripts": { "test": "pnpm test:parallel --quiet && pnpm test:serial --quiet", - "test:parallel": "playwright test ./tests/**/*.p.spec.ts --project=chromium", + "test:parallel": "playwright test ./tests/*.p.spec.ts --project=chromium && playwright test ./tests/**/*.p.spec.ts --project=chromium", "test:serial": "playwright test ./tests/**/*.s.spec.ts --project=chromium --workers=1", "test:enterprise": "ENTERPRISE=1 playwright test ./tests/enterprise/*.spec.ts --project=chromium --workers=1", "test:keycloak": "ENTERPRISE=1 playwright test ./tests/keycloak.setup.ts --workers=1", diff --git a/playwright/tests/bucket-explorer.p.spec.ts b/playwright/tests/bucket-explorer.p.spec.ts index 012857fcad..675881ffbc 100644 --- a/playwright/tests/bucket-explorer.p.spec.ts +++ b/playwright/tests/bucket-explorer.p.spec.ts @@ -33,19 +33,19 @@ test('navigate config bucket', async ({ page, utils }) => { await page.getByText('config').click() await expect(page).toHaveURL(/.*\/tools\/bucketexplorer\/config%2F/) await page.getByRole('cell', { name: 'DEFAULT' }).click() - await expect(page.locator('[data-test="file-path"]')).toHaveText( + await expect(page.locator('[data-test="file-path"]')).toContainText( '/ DEFAULT /', ) await expect(page).toHaveURL(/.*\/tools\/bucketexplorer\/config%2FDEFAULT%2F/) await page.getByRole('cell', { name: 'targets', exact: true }).click() - await expect(page.locator('[data-test="file-path"]')).toHaveText( + await expect(page.locator('[data-test="file-path"]')).toContainText( '/ DEFAULT / targets /', ) await expect(page).toHaveURL( /.*\/tools\/bucketexplorer\/config%2FDEFAULT%2Ftargets%2F/, ) await page.getByRole('cell', { name: 'INST', exact: true }).click() - await expect(page.locator('[data-test="file-path"]')).toHaveText( + await expect(page.locator('[data-test="file-path"]')).toContainText( '/ DEFAULT / targets / INST /', ) await expect(page).toHaveURL( @@ -71,14 +71,14 @@ test('navigate config bucket', async ({ page, utils }) => { ) await page.locator('[data-test="be-nav-back"]').click() - await expect(page.locator('[data-test="file-path"]')).toHaveText( + await expect(page.locator('[data-test="file-path"]')).toContainText( '/ DEFAULT / targets /', ) await expect(page).toHaveURL( /.*\/tools\/bucketexplorer\/config%2FDEFAULT%2Ftargets%2F/, ) await page.locator('[data-test="be-nav-back"]').click() - await expect(page.locator('[data-test="file-path"]')).toHaveText( + await expect(page.locator('[data-test="file-path"]')).toContainText( '/ DEFAULT /', ) await expect(page).toHaveURL(/.*\/tools\/bucketexplorer\/config%2FDEFAULT%2F/) @@ -96,7 +96,7 @@ test('navigate gems volume', async ({ page, utils }) => { // Note the URL is prefixed with %2F, i.e. '/' await expect(page).toHaveURL(/.*\/tools\/bucketexplorer\/%2Fgems%2F/) await page.getByRole('cell', { name: 'cosmoscache' }).click() - await expect(page.locator('[data-test="file-path"]')).toHaveText( + await expect(page.locator('[data-test="file-path"]')).toContainText( '/ cosmoscache /', ) await expect(page).toHaveURL( @@ -106,11 +106,11 @@ test('navigate gems volume', async ({ page, utils }) => { await page.locator('[data-test="search-input"] input').fill('bucket') await expect(page.locator('tbody > tr')).toHaveCount(1) // Download the file - await utils.download(page, 'tbody > tr [data-test="download-file"]') + await utils.download(page, 'tbody > tr [data-test="download-file"] >> nth=0') // Reload and ensure we get to the same place await page.reload() - await expect(page.locator('[data-test="file-path"]')).toHaveText( + await expect(page.locator('[data-test="file-path"]')).toContainText( '/ cosmoscache /', ) await expect(page).toHaveURL( @@ -120,7 +120,8 @@ test('navigate gems volume', async ({ page, utils }) => { await expect(page.locator('tbody > tr')).toHaveCount(1) }) -test('direct URLs', async ({ page }) => { +test('direct URLs', async ({ page, utils }) => { + await utils.sleep(500) // Wait for the app to be ready // Verify using slashes rather than %2F works await page.goto('/tools/bucketexplorer/config%2FDEFAULT%2Ftargets%2F') await expect(page.locator('.v-app-bar')).toContainText('Bucket Explorer') @@ -152,7 +153,7 @@ test('view file', async ({ page, utils }) => { await expect(page.locator('pre')).toContainText('create_timeline') await page.getByRole('button', { name: 'Ok' }).click() await page.locator('[data-test="search-input"] input').fill('') - await page.getByText('/ INST').click() + await page.getByText('INST', { exact: true }).click() await utils.sleep(500) // Allow the page to render await page.locator('[data-test="search-input"] input').fill('target.txt') await page.locator('[data-test="view-file"]').first().click() @@ -161,7 +162,23 @@ test('view file', async ({ page, utils }) => { }) test('upload and delete', async ({ page, utils }) => { - const randomDir = 'tmp_' + `${Math.random()}`.substring(2) + // Create a file so we have something in __TEMP__ + await page.goto('/tools/scriptrunner') + await expect(page.locator('.v-app-bar')).toContainText('Script Runner') + await page.locator('[data-test=script-runner-file]').click() + await page.locator('text=New File').click() + await expect(page.locator('textarea')).toHaveText('') + await page.locator('textarea').fill(`print('hello world')`) + await page.locator('[data-test=script-runner-file]').click() + await page.locator('[data-test=start-button]').click() + await expect(page.locator('[data-test=state] input')).toHaveValue( + 'completed', + { + timeout: 30000, + }, + ) + + await page.goto('/tools/bucketexplorer') await page.getByText('config').click() await expect(page).toHaveURL(/.*\/tools\/bucketexplorer\/config%2F/) await expect(page.locator('[data-test="file-path"]')).toHaveText('/') @@ -169,24 +186,14 @@ test('upload and delete', async ({ page, utils }) => { await expect(page.locator('[data-test="file-path"]')).toHaveText( '/ DEFAULT /', ) - - // Upload something to make sure the tmp dir exists - await expect(page.getByLabel('prepended action')).toBeVisible() - const [fileChooser1] = await Promise.all([ - page.waitForEvent('filechooser'), - await page.getByLabel('prepended action').click(), - ]) - await fileChooser1.setFiles('package.json') - await page - .locator('[data-test="upload-file-path"] input') - .fill(`DEFAULT/${randomDir}/tmp.json`) - await page.locator('[data-test="upload-file-submit-btn"]').click() - + await page.getByRole('cell', { name: 'targets_modified' }).click() await expect(page.locator('[data-test="file-path"]')).toHaveText( - `/ DEFAULT / ${randomDir} /`, + '/ DEFAULT / targets_modified /', + ) + await page.getByRole('cell', { name: '__TEMP__' }).click() + await expect(page.locator('[data-test="file-path"]')).toHaveText( + '/ DEFAULT / targets_modified / __TEMP__ /', ) - await page.locator('tbody> tr').first().waitFor() - let count = await page.locator('tbody > tr').count() // Note that Promise.all prevents a race condition // between clicking and waiting for the file chooser. @@ -200,49 +207,11 @@ test('upload and delete', async ({ page, utils }) => { await fileChooser.setFiles('package.json') await page.locator('[data-test="upload-file-submit-btn"]').click() - await expect - .poll(() => page.locator('tbody > tr').count(), { timeout: 10000 }) - .toBeGreaterThanOrEqual(count + 1) await expect(page.getByRole('cell', { name: 'package.json' })).toBeVisible() await page .locator('tr:has-text("package.json") [data-test="delete-file"]') .click() await page.locator('[data-test="confirm-dialog-delete"]').click() - await expect - .poll(() => page.locator('tbody > tr').count(), { timeout: 10000 }) - .toBeGreaterThanOrEqual(count) - - // Note that Promise.all prevents a race condition - // between clicking and waiting for the file chooser. - await expect(page.getByLabel('prepended action')).toBeVisible() - const [fileChooser2] = await Promise.all([ - // It is important to call waitForEvent before click to set up waiting. - page.waitForEvent('filechooser'), - // Opens the file chooser. - await page.getByLabel('prepended action').click(), - ]) - await fileChooser2.setFiles('package.json') - await page - .locator('[data-test="upload-file-path"] input') - .fill(`DEFAULT/${randomDir}/TEST/tmp/myfile.json`) - await page.locator('[data-test="upload-file-submit-btn"]').click() - await expect(page.locator('[data-test="file-path"]')).toHaveText( - `/ DEFAULT / ${randomDir} / TEST / tmp /`, - ) - await page - .locator('tr:has-text("myfile.json") [data-test="delete-file"]') - .click() - await page.locator('[data-test="confirm-dialog-delete"]').click() - - // Cleanup tmp.json - await page.getByText('logs').click() - await page.getByText('config').click() - await page.getByRole('cell', { name: 'DEFAULT' }).click() - await page.getByRole('cell', { name: randomDir }).click() - await page - .locator('tr:has-text("tmp.json") [data-test="delete-file"]') - .click() - await page.locator('[data-test="confirm-dialog-delete"]').click() }) test('navigate logs and tools bucket', async ({ page, utils }) => { @@ -284,7 +253,7 @@ test('navigate logs and tools bucket', async ({ page, utils }) => { await page.getByText('tools').click() await expect(page).toHaveURL(/.*\/tools\/bucketexplorer\/tools%2F/) if (process.env.ENTERPRISE === '1') { - await expect(page.locator('tbody > tr')).toHaveCount(20) + await expect(page.locator('tbody > tr')).toHaveCount(23) } else { await expect(page.locator('tbody > tr')).toHaveCount(17) } @@ -296,26 +265,28 @@ test('auto refreshes to update files', async ({ toolPath, context, }) => { - const randomDir = 'tmp_' + `${Math.random()}`.substring(2) - // Upload something from the first tab to make sure the tmp dir exists - await page.getByText('config').click() - await expect(page.getByLabel('prepended action')).toBeVisible() - const [fileChooser1] = await Promise.all([ - page.waitForEvent('filechooser'), - await page.getByLabel('prepended action').click(), - ]) - await fileChooser1.setFiles('package.json') - await page - .locator('[data-test="upload-file-path"] input') - .fill(`DEFAULT/${randomDir}/package1.json`) - await page.locator('[data-test="upload-file-submit-btn"]').click() + // Create a file so we have something in __TEMP__ + await page.goto('/tools/scriptrunner') + await expect(page.locator('.v-app-bar')).toContainText('Script Runner') + await page.locator('[data-test=script-runner-file]').click() + await page.locator('text=New File').click() + await expect(page.locator('textarea')).toHaveText('') + await page.locator('textarea').fill(`print('hello world')`) + await page.locator('[data-test=start-button]').click() + await expect(page.locator('[data-test=state] input')).toHaveValue( + 'completed', + { + timeout: 30000, + }, + ) - // Open another tab and navigate to the tmp dir + // Open another tab and navigate to the __TEMP__ dir const pageTwo = await context.newPage() pageTwo.goto(toolPath) await pageTwo.getByText('config').click() await pageTwo.getByRole('cell', { name: 'DEFAULT' }).click() - await pageTwo.getByRole('cell', { name: randomDir }).click() + await pageTwo.getByRole('cell', { name: 'targets_modified' }).click() + await pageTwo.getByRole('cell', { name: '__TEMP__' }).click() // Set the refresh interval on the second tab to be really slow await pageTwo.locator('[data-test=bucket-explorer-file]').click() @@ -323,27 +294,28 @@ test('auto refreshes to update files', async ({ await pageTwo .locator('.v-dialog [data-test=refresh-interval] input') .fill('1000') - await pageTwo - .locator('.v-dialog [data-test=refresh-interval] input') - .press('Enter') - await pageTwo.locator('.v-dialog').press('Escape') + await pageTwo.getByRole('button', { name: 'Save' }).click() // Upload a file from the first tab + await page.goto( + '/tools/bucketexplorer/config%2FDEFAULT%2Ftargets_modified%2F__TEMP__%2F', + ) + await expect(page.locator('.v-app-bar')).toContainText('Bucket Explorer') await expect(page.getByLabel('prepended action')).toBeVisible() - const [fileChooser2] = await Promise.all([ + const [fileChooser] = await Promise.all([ page.waitForEvent('filechooser'), await page.getByLabel('prepended action').click(), ]) - await fileChooser2.setFiles('package.json') + await fileChooser.setFiles('package.json') await page .locator('[data-test="upload-file-path"] input') - .fill(`DEFAULT/${randomDir}/package2.json`) + .fill(`DEFAULT/targets_modified/__TEMP__/refresh_package.json`) await page.locator('[data-test="upload-file-submit-btn"]').click() // The second tab shouldn't have refreshed yet, so the file shouldn't be there await page.locator('tbody> tr').first().waitFor() await expect( - pageTwo.getByRole('cell', { name: 'package2.json' }), + pageTwo.getByRole('cell', { name: 'refresh_package.json' }), ).not.toBeVisible({ timeout: 10000 }) // Set the refresh interval on the second tab to 1s @@ -352,24 +324,17 @@ test('auto refreshes to update files', async ({ await pageTwo .locator('.v-dialog [data-test=refresh-interval] input') .fill('1') - await pageTwo - .locator('.v-dialog [data-test=refresh-interval] input') - .press('Enter') - await pageTwo.locator('.v-dialog').press('Escape') + await pageTwo.getByRole('button', { name: 'Save' }).click() // Second tab should auto refresh in 1s and then the file should be there await page.locator('tbody> tr').first().waitFor() await expect( - pageTwo.getByRole('cell', { name: 'package2.json' }), + pageTwo.getByRole('cell', { name: 'refresh_package.json' }), ).toBeVisible() // Cleanup await page - .locator('tr:has-text("package1.json") [data-test="delete-file"]') - .click() - await page.locator('[data-test="confirm-dialog-delete"]').click() - await page - .locator('tr:has-text("package2.json") [data-test="delete-file"]') + .locator('tr:has-text("refresh_package.json") [data-test="delete-file"]') .click() await page.locator('[data-test="confirm-dialog-delete"]').click() }) diff --git a/playwright/tests/command-sender.p.spec.ts b/playwright/tests/command-sender.p.spec.ts index 9c36831f3a..05e1a56d7b 100644 --- a/playwright/tests/command-sender.p.spec.ts +++ b/playwright/tests/command-sender.p.spec.ts @@ -33,7 +33,7 @@ test.describe.configure({ mode: 'serial' }) // Helper function to select a parameter dropdown async function selectValue(page, param, value) { let row = page.locator(`tr:has-text("${param}")`) - await row.getByRole('combobox').click() + await row.getByRole('combobox', { name: 'Open' }).click({ force: true }) await page.getByRole('option', { name: value }).click() } @@ -75,6 +75,7 @@ test('selects a target and packet', async ({ page, utils }) => { await page.locator('[data-test="select-send"]').click() await expect(page.locator('main')).toContainText('cmd("INST ABORT") sent') // Test the autocomplete by typing in a command + await page.locator('[data-test=select-packet]').click() await page.locator('[data-test=select-packet] input[type="text"]').fill('COL') await page.locator('span:has-text("COL")').click() await expect(page.locator('main')).toContainText('Starts a collect') @@ -128,6 +129,7 @@ test('warns for hazardous commands', async ({ page, utils }) => { // 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 page.locator('[data-test="select-send"]').click() await page.getByRole('dialog').getByRole('button', { name: 'Send' }).click() @@ -139,6 +141,7 @@ test('warns for hazardous commands', async ({ page, utils }) => { // 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 page.locator('[data-test="select-send"]').click() await page.getByRole('dialog').getByRole('button', { name: 'Send' }).click() @@ -150,6 +153,7 @@ test('warns for hazardous commands', async ({ page, utils }) => { // 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 page.locator('[data-test="select-send"]').click() await page.getByRole('dialog').getByRole('button', { name: 'Send' }).click() @@ -218,6 +222,7 @@ test('handles NaN and Infinite values', async ({ page, utils }) => { // 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 expect(page.locator('main')).toContainText( @@ -324,7 +329,7 @@ test('handles string values', async ({ page, utils }) => { await expect(page.locator('main')).toContainText('ASCII command') // The default text 'NOOP' should be selected let row = page.locator(`tr:has-text("STRING")`) - await expect(row.getByRole('combobox')).toContainText('NOOP') + await expect(row.getByRole('combobox', { name: 'Open' })).toHaveValue('NOOP') await checkValue(page, 'STRING', 'NOOP') await page.locator('[data-test="select-send"]').click() await expect(page.locator('main')).toContainText( @@ -352,6 +357,7 @@ test('gets details with right click', async ({ page, utils }) => { await page.locator('text=Collect type').click({ button: 'right' }) await page.locator('text=Details').click() await expect(page.locator('.v-dialog')).toContainText('INST COLLECT TYPE') + await utils.sleep(500) // Or we might miss the escape press await page.locator('.v-dialog').press('Escape') await expect(page.locator('.v-dialog')).not.toBeVisible() }) @@ -403,6 +409,9 @@ test('executes commands from history', async ({ page, utils }) => { // Edit the existing SETPARAMS command and then send // This is somewhat fragile but not sure how else to edit 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('ArrowDown') + await page.locator('[data-test=sender-history]').press('End') await page.locator('[data-test=sender-history]').press('ArrowUp') await page.locator('[data-test=sender-history]').press('End') await page.locator('[data-test=sender-history]').press('ArrowLeft') @@ -526,6 +535,7 @@ test('ignores normal range checks', async ({ page, utils }) => { // 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', @@ -538,6 +548,7 @@ test('ignores normal range checks', async ({ page, utils }) => { // 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 utils.selectTargetPacketItem('EXAMPLE', 'START') await page.locator('[data-test=sender-history]').click() await utils.sleep(500) // Allow focus to change @@ -572,6 +583,7 @@ test('ignores hazardous range checks', async ({ page, utils }) => { // 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 page.getByRole('dialog').getByRole('button', { name: 'Send' }).click() // Hazardous confirm await expect(page.locator('main')).toContainText( @@ -599,6 +611,7 @@ test('shows ignored parameters', async ({ page, utils }) => { await expect(page.locator('main')).not.toContainText('Parameters') await page.locator('[data-test=command-sender-mode]').click() await page.locator('text=Show Ignored').click() + await page.locator('[data-test=command-sender-mode]').click() await expect(page.locator('main')).toContainText('Parameters') // Now the parameters table is shown await expect(page.locator('main')).toContainText('CCSDSVER') // CCSDSVER is one of the parameters }) @@ -639,6 +652,7 @@ test('disable parameter conversions', async ({ page, utils }) => { await expect(page.locator('.v-app-bar')).toContainText('Command Sender') 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 utils.selectTargetPacketItem('INST', 'SETPARAMS') await page.locator('[data-test="select-send"]').click() @@ -652,6 +666,7 @@ test('disable parameter conversions', async ({ page, utils }) => { // Disable range checks just to verify the command history 'cmd_raw_no_range_check' 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 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', @@ -688,9 +703,10 @@ test('disables command validation', async ({ page, utils }) => { await page.locator('[data-test=command-sender-mode]').click() await page.getByText('Disable Command Validation').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("INST TIME_OFFSET with SECONDS 0, IP_ADDRESS \'127.0.0.1\'") sent', + 'cmd("INST TIME_OFFSET with SECONDS 0, IP_ADDRESS \'127.0.0.1\'", validate=False) sent', ) }) diff --git a/playwright/tests/data-extractor.p.spec.ts b/playwright/tests/data-extractor.p.spec.ts index 7b8daa22f4..9de1fc394e 100644 --- a/playwright/tests/data-extractor.p.spec.ts +++ b/playwright/tests/data-extractor.p.spec.ts @@ -162,7 +162,7 @@ test('cancels a process', async ({ page, utils }) => { test('adds an entire target', async ({ page, utils }) => { await utils.addTargetPacketItem('INST') - await expect(page.getByText('1-20 of 161')).toBeVisible() + await expect(page.getByText('1-20 of 234')).toBeVisible() }) test('adds an entire packet', async ({ page, utils }) => { @@ -305,6 +305,7 @@ test('outputs full column names', async ({ page, utils }) => { // Switch back and verify await page.locator('[data-test=data-extractor-mode]').click() await page.locator('text=Normal Columns').click() + await page.locator('[data-test=data-extractor-mode]').click() // Create a new end time so we get a new filename start = sub(new Date(), { minutes: 2 }) await page @@ -319,6 +320,7 @@ test('fills values', async ({ page, utils }) => { const start = sub(new Date(), { minutes: 1 }) await page.locator('[data-test=data-extractor-mode]').click() await page.locator('text=Fill Down').click() + await page.locator('[data-test=data-extractor-mode]').click() await page .locator('[data-test=start-time] input') .fill(format(start, 'HH:mm:ss')) diff --git a/playwright/tests/data-viewer.p.spec.ts b/playwright/tests/data-viewer.p.spec.ts index dfb1fdc1da..3451c15772 100644 --- a/playwright/tests/data-viewer.p.spec.ts +++ b/playwright/tests/data-viewer.p.spec.ts @@ -27,6 +27,7 @@ test.use({ async function addComponent(page, utils, target: string, packet: string) { await page.locator('[data-test=new-tab]').click() + await utils.sleep(500) // wait for the component selector to be clickable await utils.selectTargetPacketItem(target, packet) await page.locator('[data-test=select-send]').click() // add the packet to the list await page.locator('[data-test=add-component]').click() @@ -83,7 +84,8 @@ test('saves, opens, and resets the configuration', async ({ page, utils }) => { await page.getByRole('button', { name: 'Dismiss' }).click({ timeout: 20000 }) // Verify the config - await page.getByRole('tab', { name: 'Test1' }).click() + await expect(page.getByText('Current Time:')).toBeVisible() + await page.getByRole('tab', { name: 'Test2' }).getByRole('button').click() await expect(page.getByText('COSMOS Packet Raw/Decom')).toBeVisible() // Verify display setting await page.locator('[data-test=history-component-open-settings]').click() @@ -95,8 +97,6 @@ test('saves, opens, and resets the configuration', async ({ page, utils }) => { await expect( page.locator('[data-test=display-settings-card]'), ).not.toBeVisible() - await page.getByRole('tab', { name: 'Test2' }).click() - await expect(page.getByText('Current Time:')).toBeVisible() // Reset this test configuration await page.locator('[data-test=data-viewer-file]').click() @@ -192,7 +192,10 @@ test('deletes a component and tab', async ({ page, utils }) => { await expect( page.getByRole('tab', { name: 'INST ADCS [ RAW ]' }), ).toBeVisible() - await page.locator('[data-test=delete-component]').click() + await page + .getByRole('tab', { name: 'INST ADCS [ RAW ]' }) + .getByRole('button') + .click() await expect(page.locator('.v-card > .v-card-title').first()).toHaveText( "You're not viewing any packets", ) @@ -411,6 +414,7 @@ test('displays event message format', async ({ page, utils }) => { await page.locator('[data-test="new-tab"]').click() await page.locator('[data-test="select-component"]').click() await page.getByText('COSMOS Event Message').click() + await utils.sleep(500) // wait for the packet/item selector to be clickable await utils.selectTargetPacketItem('INST', 'HEALTH_STATUS', 'CCSDSVER') await page.locator('button:has-text("Add Item")').click() await page.locator('[data-test=add-component]').click() @@ -419,14 +423,11 @@ test('displays event message format', async ({ page, utils }) => { await expect(page.locator('[data-test=history-component-text-area] textarea')) // Create regular expression to match the line: // Time: 2024-10-01T01:15:49.419Z TEMP1: -1.119 C - .toHaveValue( - /\d*/, - ) + .toHaveValue(/\d*/) await expect(page.locator('[data-test=history-component-text-area] textarea')) // Create regular expression to match the line: // Time: 2024-10-01T01:15:49.419Z TEMP1: -1.119 C - .not - .toHaveValue( + .not.toHaveValue( /Time: \d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{3}Z\s+CCSDSVER: .*/, ) }) diff --git a/playwright/tests/packet-viewer.p.spec.ts b/playwright/tests/packet-viewer.p.spec.ts index ad9fa9031b..9733172a44 100644 --- a/playwright/tests/packet-viewer.p.spec.ts +++ b/playwright/tests/packet-viewer.p.spec.ts @@ -77,6 +77,10 @@ test('routes to a packet with a period in the name', async ({ }) test('selects a target and packet to display', async ({ page, utils }) => { + await page.goto('/tools/packetviewer/EXAMPLE/STATUS/') + await expect(page.locator('tr:has(td:nth-child(2))').nth(0)).toContainText( + 'PACKET_TIMESECONDS *', + ) await utils.selectTargetPacketItem('INST', 'IMAGE') await utils.dropdownSelectedValue(page, '[data-test=select-target]', 'INST') await utils.dropdownSelectedValue(page, '[data-test=select-packet]', 'IMAGE') @@ -87,8 +91,8 @@ test('selects a target and packet to display', async ({ page, utils }) => { }) test('gets help info', async ({ page, utils }) => { - await utils.selectTargetPacketItem('INST', 'IMAGE') - await page.locator('.v-data-table-footer > i').hover() + await page.goto('/tools/packetviewer/INST/IMAGE/') + await page.locator('.mdi-information-variant-circle').hover() await expect(page.getByText('Name with * indicates DERIVED')).toBeVisible() await expect(page.getByText('Right click name to pin item')).toBeVisible() await expect( @@ -103,7 +107,7 @@ test('gets help info', async ({ page, utils }) => { }) test('gets details with right click', async ({ page, utils }) => { - await utils.selectTargetPacketItem('INST', 'HEALTH_STATUS') + await page.goto('/tools/packetviewer/INST/HEALTH_STATUS/') await page .getByRole('cell', { name: 'TEMP1', exact: true }) .locator('xpath=..') // parent row @@ -152,7 +156,7 @@ test('gets details with right click', async ({ page, utils }) => { }) test('stops posting to the api after closing', async ({ page, utils }) => { - await utils.selectTargetPacketItem('INST', 'ADCS') + await page.goto('/tools/packetviewer/INST/ADCS/') let requestCount = 0 page.on('request', () => { requestCount++ @@ -169,7 +173,7 @@ test('stops posting to the api after closing', async ({ page, utils }) => { // Changing the polling rate is fraught with danger because it's all // about waiting for changes and detecting changes test('changes the polling rate', async ({ page, utils }) => { - await utils.selectTargetPacketItem('INST', 'HEALTH_STATUS') + await page.goto('/tools/packetviewer/INST/HEALTH_STATUS/') await page.locator('[data-test=packet-viewer-file]').click() await page.locator('[data-test=packet-viewer-file-options]').click() await page @@ -207,19 +211,21 @@ test('displays formatted items with units by default', async ({ page, utils, }) => { - await utils.selectTargetPacketItem('INST', 'HEALTH_STATUS') + await page.goto('/tools/packetviewer/INST/HEALTH_STATUS/') // Check for exactly 3 decimal points followed by units await matchItem(page, 'TEMP1', /^-?\d+\.\d{3}\s\S$/) }) test('searches on packets without data', async ({ page, utils }) => { - await utils.selectTargetPacketItem('EXAMPLE', 'STATUS') + await page.goto('/tools/packetviewer/EXAMPLE/STATUS/') await page.locator('[data-test="search"] input').fill('STRING') - await expect.poll(() => page.locator('tbody > tr').count()).toEqual(1) + await expect + .poll(() => page.locator('tr:has(td:nth-child(2))').count()) + .toEqual(1) }) test('displays formatted items with units', async ({ page, utils }) => { - await utils.selectTargetPacketItem('INST', 'HEALTH_STATUS') + await page.goto('/tools/packetviewer/INST/HEALTH_STATUS/') await page.locator('[data-test="search"] input').fill('TEMP1') await page.locator('[data-test=packet-viewer-view]').click() await page.locator('text=Formatted Items with Units').click() @@ -228,7 +234,7 @@ test('displays formatted items with units', async ({ page, utils }) => { }) test('displays raw items', async ({ page, utils }) => { - await utils.selectTargetPacketItem('INST', 'HEALTH_STATUS') + await page.goto('/tools/packetviewer/INST/HEALTH_STATUS/') await page.locator('[data-test="search"] input').fill('TEMP1') await page.locator('[data-test=packet-viewer-view]').click() await page.locator('text=Raw').click() @@ -237,7 +243,7 @@ test('displays raw items', async ({ page, utils }) => { }) test('displays converted items', async ({ page, utils }) => { - await utils.selectTargetPacketItem('INST', 'HEALTH_STATUS') + await page.goto('/tools/packetviewer/INST/HEALTH_STATUS/') await page.locator('[data-test="search"] input').fill('TEMP1') await page.locator('[data-test=packet-viewer-view]').click() await page.locator('text=Converted').click() @@ -246,7 +252,7 @@ test('displays converted items', async ({ page, utils }) => { }) test('displays formatted items', async ({ page, utils }) => { - await utils.selectTargetPacketItem('INST', 'HEALTH_STATUS') + await page.goto('/tools/packetviewer/INST/HEALTH_STATUS/') await page.locator('[data-test="search"] input').fill('TEMP1') await page.locator('[data-test=packet-viewer-view]').click() // Use text-is because we have to match exactly since there is @@ -257,77 +263,111 @@ test('displays formatted items', async ({ page, utils }) => { }) test('shows ignored items', async ({ page, utils }) => { - await utils.selectTargetPacketItem('INST', 'HEALTH_STATUS') + await page.goto('/tools/packetviewer/INST/HEALTH_STATUS/') await page.locator('[data-test=packet-viewer-view]').click() await page.locator('text=Display Derived').click() - await expect(page.locator('text=Display Derived')).not.toBeVisible() + await page.locator('[data-test=packet-viewer-view]').click() await expect(page.getByRole('cell', { name: 'CCSDSVER' })).not.toBeVisible() await page.locator('[data-test=packet-viewer-view]').click() await page.locator('text=Show Ignored').click() - await expect(page.locator('text=Show Ignored')).not.toBeVisible() + await page.locator('[data-test=packet-viewer-view]').click() await expect(page.getByRole('cell', { name: 'CCSDSVER' })).toBeVisible() await page.locator('[data-test=packet-viewer-view]').click() await page.locator('text=Show Ignored').click() + await page.locator('[data-test=packet-viewer-view]').click() await expect(page.getByRole('cell', { name: 'CCSDSVER' })).not.toBeVisible() }) test('displays derived first', async ({ page, utils }) => { - await utils.selectTargetPacketItem('INST', 'HEALTH_STATUS') - // First row (0) is the header: Index, Name, Value - await expect(page.locator('tr').nth(1)).toContainText('PACKET_TIMESECONDS') + await page.goto('/tools/packetviewer/INST/HEALTH_STATUS/') + await expect(page.locator('tr:has(td:nth-child(2))').nth(0)).toContainText( + 'PACKET_TIMESECONDS', + ) await page.locator('[data-test=packet-viewer-view]').click() await page.locator('text=Display Derived').click() - await expect(page.locator('text=Display Derived')).not.toBeVisible() - await expect(page.locator('tr').nth(1)).toContainText('TIMESEC') - // Check 2 because TIMESEC is included in PACKET_ONDS + await page.locator('[data-test=packet-viewer-view]').click() + await expect(page.locator('tr:has(td:nth-child(2))').nth(0)).toContainText( + 'TIMESEC', + ) + // Check 3 because TIMESEC is included in PACKET_ONDS // so the first check could result in a false positive - await expect(page.locator('tr').nth(2)).toContainText('TIMEUS') + await expect(page.locator('tr:has(td:nth-child(2))').nth(1)).toContainText( + 'TIMEUS', + ) }) test('pins items to the top of the list', async ({ page, utils }) => { - await utils.selectTargetPacketItem('INST', 'HEALTH_STATUS') + await page.goto('/tools/packetviewer/INST/HEALTH_STATUS/') // Default sort order - await expect(page.locator('tr').nth(1)).toContainText('PACKET_TIMESECONDS *') + await expect(page.locator('tr:has(td:nth-child(2))').nth(0)).toContainText( + 'PACKET_TIMESECONDS *', + ) await page.getByText('PACKET_TIME *').click({ button: 'right', }) await page.getByText('Pin Item', { exact: true }).click() - await expect(page.locator('tr').nth(1)).toContainText('PACKET_TIME *') + await expect(page.locator('tr:has(td:nth-child(2))').nth(0)).toContainText( + 'PACKET_TIME *', + ) // Verify pin is not affected by sorting await page.getByText('Name', { exact: true }).click() - await expect(page.locator('tr').nth(1)).toContainText('PACKET_TIME *') + await expect(page.locator('tr:has(td:nth-child(2))').nth(0)).toContainText( + 'PACKET_TIME *', + ) await page.getByText('Name', { exact: true }).click() - await expect(page.locator('tr').nth(1)).toContainText('PACKET_TIME *') + await expect(page.locator('tr:has(td:nth-child(2))').nth(0)).toContainText( + 'PACKET_TIME *', + ) await page.getByText('Name', { exact: true }).click() - await expect(page.locator('tr').nth(1)).toContainText('PACKET_TIME *') + await expect(page.locator('tr:has(td:nth-child(2))').nth(0)).toContainText( + 'PACKET_TIME *', + ) await page.getByText('Value', { exact: true }).click() - await expect(page.locator('tr').nth(1)).toContainText('PACKET_TIME *') + await expect(page.locator('tr:has(td:nth-child(2))').nth(0)).toContainText( + 'PACKET_TIME *', + ) await page.getByText('Value', { exact: true }).click() - await expect(page.locator('tr').nth(1)).toContainText('PACKET_TIME *') + await expect(page.locator('tr:has(td:nth-child(2))').nth(0)).toContainText( + 'PACKET_TIME *', + ) await page.getByText('Value', { exact: true }).click() - await expect(page.locator('tr').nth(1)).toContainText('PACKET_TIME *') + await expect(page.locator('tr:has(td:nth-child(2))').nth(0)).toContainText( + 'PACKET_TIME *', + ) await page.locator('[data-test="search"] input').fill('GROUND') - await expect(page.locator('tr').nth(1)).toContainText('PACKET_TIME *') + await expect(page.locator('tr:has(td:nth-child(2))').nth(0)).toContainText( + 'PACKET_TIME *', + ) await page.getByText('GROUND1STATUS', { exact: true }).click({ button: 'right', }) await page.getByText('Pin Item', { exact: true }).click() await page.locator('[data-test="search"] input').fill('') // Default sort order is packet order so PACKET_TIME is first - await expect(page.locator('tr').nth(1)).toContainText('PACKET_TIME *') - await expect(page.locator('tr').nth(2)).toContainText('GROUND1STATUS') + await expect(page.locator('tr:has(td:nth-child(2))').nth(0)).toContainText( + 'PACKET_TIME *', + ) + await expect(page.locator('tr:has(td:nth-child(2))').nth(1)).toContainText( + 'GROUND1STATUS', + ) await page.getByText('GROUND1STATUS', { exact: true }).click({ button: 'right', }) await page.getByText('Unpin Item').click() - await expect(page.locator('tr').nth(1)).toContainText('PACKET_TIME *') - await expect(page.locator('tr').nth(2)).not.toContainText('GROUND1STATUS') + await expect(page.locator('tr:has(td:nth-child(2))').nth(0)).toContainText( + 'PACKET_TIME *', + ) + await expect( + page.locator('tr:has(td:nth-child(2))').nth(1), + ).not.toContainText('GROUND1STATUS') await page.locator('[data-test="packet-viewer-file"]').click() await page.getByText('Reset Configuration').click() // Return to default sort order - await expect(page.locator('tr').nth(1)).toContainText('PACKET_TIMESECONDS *') + await expect(page.locator('tr:has(td:nth-child(2))').nth(0)).toContainText( + 'PACKET_TIMESECONDS *', + ) }) diff --git a/playwright/tests/tlm-viewer.p.spec.ts b/playwright/tests/tlm-viewer.p.spec.ts index 085745eb9e..29d619faef 100644 --- a/playwright/tests/tlm-viewer.p.spec.ts +++ b/playwright/tests/tlm-viewer.p.spec.ts @@ -57,17 +57,10 @@ async function showScreen( test('changes targets', async ({ page, utils }) => { await page.locator('[data-test="select-target"]').click() - await page.getByText('SYSTEM').click() - await expect( - page.locator('.v-select__content [role="listbox"]'), - ).not.toBeVisible() - await page.locator('[data-test="select-screen"]').click() - await expect(page.locator('.v-select__content [role="listbox"]')).toHaveText( + await page.getByRole('option', { name: 'SYSTEM', exact: true }).click() + await expect(page.locator('[data-test="select-screen"]')).toContainText( 'STATUS', ) - await expect( - page.locator('.v-select__content [role="listbox"]'), - ).not.toHaveText('ADCS') }) test('displays INST ADCS', async ({ page, utils }) => { diff --git a/playwright/tests/utc-time.p.spec.ts b/playwright/tests/utc-time.p.spec.ts index 977dd9b346..0dc7c382f8 100644 --- a/playwright/tests/utc-time.p.spec.ts +++ b/playwright/tests/utc-time.p.spec.ts @@ -381,7 +381,7 @@ test.describe('Telemetry Grapher', () => { .click() await expect(page.getByText('Add a graph')).toBeVisible() await page.locator('[data-test=telemetry-grapher-graph]').click() - await page.locator('text=Add Graph').click() + await page.locator('[data-test=telemetry-grapher-graph-add-graph]').click() await utils.selectTargetPacketItem('INST', 'HEALTH_STATUS', 'TEMP1') await page.locator('button:has-text("Add Item")').click() await page.locator('#innerapp button').first().click() diff --git a/playwright/utilities.ts b/playwright/utilities.ts index 08a58b1104..3a2cb438b0 100644 --- a/playwright/utilities.ts +++ b/playwright/utilities.ts @@ -30,6 +30,9 @@ export class Utilities { } async selectTargetPacketItem(target: string, packet?: string, item?: string) { + await expect( + this.page.locator('[data-test="select-target"]'), + ).not.toBeEmpty() await this.page.locator('[data-test=select-target]').click() await this.page.getByRole('option', { name: target, exact: true }).click() await expect(