From faed27c143cf14ca41d00627a7b0419de33b32a3 Mon Sep 17 00:00:00 2001 From: Jesse Mazzella Date: Wed, 13 Mar 2024 13:27:49 -0700 Subject: [PATCH] fix(#7552): Fix notebook snapshot image annotations (#7555) * fix: painterro import * test(snapshotAnnotation): add minimal e2e test * chore: add e2e test annotation * fix: notebook snapshot test * refactor: put `v-else` on template * small changes to the test and a visual one * additional a11y * fix: html structure * test(e2e): fix notebook snapshot tests * Update documentation for file download and JSON testing * Update stubs and add jpg/png export * refactor(TimelistComponent): tidy up --------- Co-authored-by: John Hill --- e2e/README.md | 24 ++++ .../flexibleLayout/flexibleLayout.e2e.spec.js | 4 +- .../notebook/notebookSnapshots.e2e.spec.js | 116 ++++++++++++------ e2e/tests/visual-a11y/notebook.visual.spec.js | 40 +++++- .../overlays/components/OverlayComponent.vue | 7 +- .../notebook/components/NotebookEmbed.vue | 1 + .../components/snapshot-template.html | 36 +++--- .../notebook/utils/painterroInstance.js | 2 +- src/plugins/timelist/TimelistComponent.vue | 93 +++++++------- 9 files changed, 206 insertions(+), 117 deletions(-) diff --git a/e2e/README.md b/e2e/README.md index 22c41d86d5..65e4a9333b 100644 --- a/e2e/README.md +++ b/e2e/README.md @@ -516,6 +516,30 @@ test.describe('foo test suite', () => { - Working with multiple pages There are instances where multiple browser pages will needed to verify multi-page or multi-tab application behavior. Make sure to use the `@2p` annotation as well as name each page appropriately: i.e. `page1` and `page2` or `tab1` and `tab2` depending on the intended use case. Generally pages should be used unless testing `sharedWorker` code, specifically. +- Working with file downloads and JSON data +Open MCT has the capability of exporting certain objects in the form of a JSON file handled by the chrome browser. The best example of this type of test can be found in the exportAsJson test. + +```js +const [download] = await Promise.all([ + page.waitForEvent('download'), // Waits for the download event + page.getByLabel('Export as JSON').click() // Triggers the download +]); + +// Wait for the download process to complete +const path = await download.path(); + +// Read the contents of the downloaded file using readFile from fs/promises +const fileContents = await fs.readFile(path, 'utf8'); +const jsonData = JSON.parse(fileContents); + +// Use the function to retrieve the key +const key = getFirstKeyFromOpenMctJson(jsonData); + +// Verify the contents of the JSON file +expect(jsonData.openmct[key]).toHaveProperty('name', 'e2e folder'); +``` + + ### Reporting Test Reporting is done through official Playwright reporters and the CI Systems which execute them. diff --git a/e2e/tests/functional/plugins/flexibleLayout/flexibleLayout.e2e.spec.js b/e2e/tests/functional/plugins/flexibleLayout/flexibleLayout.e2e.spec.js index 17ad06b78a..9024199953 100644 --- a/e2e/tests/functional/plugins/flexibleLayout/flexibleLayout.e2e.spec.js +++ b/e2e/tests/functional/plugins/flexibleLayout/flexibleLayout.e2e.spec.js @@ -289,7 +289,7 @@ test.describe('Flexible Layout Toolbar Actions @localStorage', () => { await page.getByTitle('Add Container').click(); expect(await containerHandles.count()).toEqual(3); await page.getByTitle('Remove Container').click(); - await expect(page.getByRole('dialog', { name: 'Overlay' })).toHaveText( + await expect(page.getByRole('dialog', { name: 'Overlay' })).toContainText( 'This action will permanently delete this container from this Flexible Layout. Do you want to continue?' ); await page.getByRole('button', { name: 'OK', exact: true }).click(); @@ -299,7 +299,7 @@ test.describe('Flexible Layout Toolbar Actions @localStorage', () => { expect(await page.getByRole('group', { name: 'Frame' }).count()).toEqual(2); await page.getByRole('group', { name: 'Child Layout 1' }).click(); await page.getByTitle('Remove Frame').click(); - await expect(page.getByRole('dialog', { name: 'Overlay' })).toHaveText( + await expect(page.getByRole('dialog', { name: 'Overlay' })).toContainText( 'This action will remove this frame from this Flexible Layout. Do you want to continue?' ); await page.getByRole('button', { name: 'OK', exact: true }).click(); diff --git a/e2e/tests/functional/plugins/notebook/notebookSnapshots.e2e.spec.js b/e2e/tests/functional/plugins/notebook/notebookSnapshots.e2e.spec.js index 377b078366..8369012c7c 100644 --- a/e2e/tests/functional/plugins/notebook/notebookSnapshots.e2e.spec.js +++ b/e2e/tests/functional/plugins/notebook/notebookSnapshots.e2e.spec.js @@ -71,42 +71,89 @@ test.describe('Snapshot Container tests', () => { test.beforeEach(async ({ page }) => { //Navigate to baseURL await page.goto('./', { waitUntil: 'domcontentloaded' }); - - // Create Notebook - // const notebook = await createDomainObjectWithDefaults(page, { - // type: 'Notebook', - // name: "Test Notebook" - // }); - // // Create Overlay Plot - // const snapShotObject = await createDomainObjectWithDefaults(page, { - // type: 'Overlay Plot', - // name: "Dropped Overlay Plot" - // }); - await page.getByLabel('Open the Notebook Snapshot Menu').click(); await page.getByRole('menuitem', { name: 'Save to Notebook Snapshots' }).click(); await page.getByLabel('Show Snapshots').click(); }); test('A snapshot can be Quick Viewed from Container with 3 dot action menu', async ({ page }) => { - await page.locator('.c-snapshot.c-ne__embed').first().getByTitle('More actions').click(); + await page.getByLabel('My Items Notebook Embed').getByLabel('More actions').click(); await page.getByRole('menuitem', { name: 'Quick View' }).click(); - await expect(page.locator('.c-overlay__outer')).toBeVisible(); + await expect(page.getByLabel('Modal Overlay')).toBeVisible(); + await expect(page.getByLabel('Preview Container')).toBeVisible(); + }); + test('A snapshot can be Viewed, Annotated, display deleted, and saved from Container with 3 dot action menu', async ({ + page + }) => { + test.info().annotations.push({ + type: 'issue', + description: 'https://github.com/nasa/openmct/issues/7552' + }); + //Open Snapshot Object View + await page.getByLabel('My Items Notebook Embed').getByLabel('More actions').click(); + await page.getByRole('menuitem', { name: 'View Snapshot' }).click(); + await expect(page.getByRole('dialog', { name: 'Modal Overlay' })).toBeVisible(); + await expect(page.locator('#snapshotDescriptor')).toHaveText( + /SNAPSHOT \d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}/ + ); + // Open Annotation Editor with Painterro + await page.getByLabel('Annotate this snapshot').click(); + await expect(page.locator('#snap-annotation-canvas')).toBeVisible(); + // Clear the canvas + await page.getByRole('button', { name: 'Put text [T]' }).click(); + // Click in the Painterro canvas to add a text annotation + await page.locator('.ptro-crp-el').click(); + await page.locator('.ptro-text-tool-input').fill('...is there life on mars?'); + // When working with Painterro, we need to check that the Apply button is hidden after clicking + await page.getByTitle('Apply').click(); + await expect(page.getByTitle('Apply')).toBeHidden(); + + // Save and exit annotation window + await page.getByRole('button', { name: 'Save' }).click(); + await page.getByRole('button', { name: 'Done' }).click(); + + // Open up annotation again + await page.getByRole('img', { name: 'My Items thumbnail' }).click(); + await expect(page.getByLabel('Modal Overlay').getByRole('img')).toBeVisible(); + }); + test('A snapshot can be Annotated and saved as a JPG and PNG', async ({ page }) => { + //Open Snapshot Object View + await page.getByLabel('My Items Notebook Embed').getByLabel('More actions').click(); + await page.getByRole('menuitem', { name: 'View Snapshot' }).click(); + await expect(page.getByRole('dialog', { name: 'Modal Overlay' })).toBeVisible(); + + // Open Annotation Editor with Painterro + await page.getByLabel('Annotate this snapshot').click(); + await expect(page.locator('#snap-annotation-canvas')).toBeVisible(); + // Clear the canvas + await page.getByRole('button', { name: 'Put text [T]' }).click(); + // Click in the Painterro canvas to add a text annotation + await page.locator('.ptro-crp-el').click(); + await page.locator('.ptro-text-tool-input').fill('...is there life on mars?'); + // When working with Painterro, we need to check that the Apply button is hidden after clicking + await page.getByTitle('Apply').click(); + await expect(page.getByTitle('Apply')).toBeHidden(); + + // Save and exit annotation window + await page.getByRole('button', { name: 'Save' }).click(); + await page.getByRole('button', { name: 'Done' }).click(); + + // Open up annotation again + await page.getByRole('img', { name: 'My Items thumbnail' }).click(); + await expect(page.getByLabel('Modal Overlay').getByRole('img')).toBeVisible(); + + // Save as JPG + await Promise.all([ + page.waitForEvent('download'), // Waits for the download event + page.getByLabel('Export as JPG').click() // Triggers the download + ]); + + // Save as PNG + await expect(page.getByLabel('Modal Overlay').getByRole('img')).toBeVisible(); + await Promise.all([ + page.waitForEvent('download'), // Waits for the download event + page.getByLabel('Export as PNG').click() // Triggers the download + ]); }); - test.fixme( - 'A snapshot can be Viewed, Annotated, display deleted, and saved from Container with 3 dot action menu', - async ({ page }) => { - await page.locator('.c-snapshot.c-ne__embed').first().getByTitle('More actions').click(); - await page.getByRole('menuitem', { name: ' View Snapshot' }).click(); - await expect(page.locator('.c-overlay__outer')).toBeVisible(); - await page.getByTitle('Annotate').click(); - await expect(page.locator('#snap-annotation-canvas')).toBeVisible(); - await page.getByRole('button', { name: '' }).click(); - // await expect(page.locator('#snap-annotation-canvas')).not.toBeVisible(); - await page.getByRole('button', { name: 'Save' }).click(); - await page.getByRole('button', { name: 'Done' }).click(); - //await expect(await page.locator) - } - ); test.fixme('5 Snapshots can be added to a container', async ({ page }) => {}); test.fixme( '5 Snapshots can be added to a container and Deleted with Delete All action', @@ -116,10 +163,6 @@ test.describe('Snapshot Container tests', () => { 'A snapshot can be Deleted from Container with 3 dot action menu', async ({ page }) => {} ); - test.fixme( - 'A snapshot can be Navigated To from Container with 3 dot action menu', - async ({ page }) => {} - ); test.fixme( 'A snapshot can be Navigated To Item in Time from Container with 3 dot action menu', async ({ page }) => {} @@ -151,11 +194,4 @@ test.describe('Snapshot Container tests', () => { //Snapshot removed from container? } ); - test.fixme( - 'Verify Embedded options for PNG, JPG, and Annotate work correctly', - async ({ page }) => { - //Add snapshot to container - //Verify PNG, JPG, and Annotate buttons work correctly - } - ); }); diff --git a/e2e/tests/visual-a11y/notebook.visual.spec.js b/e2e/tests/visual-a11y/notebook.visual.spec.js index c8508149d1..d04f29ccaf 100644 --- a/e2e/tests/visual-a11y/notebook.visual.spec.js +++ b/e2e/tests/visual-a11y/notebook.visual.spec.js @@ -23,7 +23,7 @@ import percySnapshot from '@percy/playwright'; import { createDomainObjectWithDefaults, expandTreePaneItemByName } from '../../appActions.js'; -import { test } from '../../avpFixtures.js'; +import { expect, test } from '../../avpFixtures.js'; import { VISUAL_URL } from '../../constants.js'; import { enterTextEntry, startAndAddRestrictedNotebookObject } from '../../helper/notebookUtils.js'; @@ -39,6 +39,44 @@ test.describe('Visual - Restricted Notebook @a11y', () => { }); }); +test.describe('Visual - Notebook Snapshot @a11y', () => { + test.beforeEach(async ({ page }) => { + await page.goto('./?hideTree=true&hideInspector=true', { waitUntil: 'domcontentloaded' }); + }); + test('Visual check for Snapshot Annotation', async ({ page, theme }) => { + await page.getByLabel('Open the Notebook Snapshot Menu').click(); + await page.getByRole('menuitem', { name: 'Save to Notebook Snapshots' }).click(); + await page.getByLabel('Show Snapshots').click(); + + await page.getByLabel('My Items Notebook Embed').getByLabel('More actions').click(); + await page.getByRole('menuitem', { name: 'View Snapshot' }).click(); + + await page.getByLabel('Annotate this snapshot').click(); + await expect(page.locator('#snap-annotation-canvas')).toBeVisible(); + // Clear the canvas + await page.getByRole('button', { name: 'Put text [T]' }).click(); + // Click in the Painterro canvas to add a text annotation + await page.locator('.ptro-crp-el').click(); + await page.locator('.ptro-text-tool-input').fill('...is there life on mars?'); + await percySnapshot(page, `Notebook Snapshot with text entry open (theme: '${theme}')`); + + // When working with Painterro, we need to check that the Apply button is hidden after clicking + await page.getByTitle('Apply').click(); + await expect(page.getByTitle('Apply')).toBeHidden(); + + // Save and exit annotation window + await page.getByRole('button', { name: 'Save' }).click(); + await page.getByRole('button', { name: 'Done' }).click(); + + // Open up annotation again + await page.getByRole('img', { name: 'My Items thumbnail' }).click(); + await expect(page.getByLabel('Modal Overlay').getByRole('img')).toBeVisible(); + + // Take a snapshot + await percySnapshot(page, `Notebook Snapshot with annotation (theme: '${theme}')`); + }); +}); + test.describe('Visual - Notebook @a11y', () => { let notebook; test.beforeEach(async ({ page }) => { diff --git a/src/api/overlays/components/OverlayComponent.vue b/src/api/overlays/components/OverlayComponent.vue index 9c2dd0736e..5b2a0dcb06 100644 --- a/src/api/overlays/components/OverlayComponent.vue +++ b/src/api/overlays/components/OverlayComponent.vue @@ -20,7 +20,7 @@ at runtime from the About dialog for additional information. --> -
- - - - +
+
+ + + + + + + - - - - - -
-
+ + + + @@ -526,20 +523,16 @@ export default { return activities.map(this.styleActivity); }, setSort() { - const sortOrder = SORT_ORDER_OPTIONS[this.domainObject.configuration.sortOrderIndex]; - const property = sortOrder.property; - const direction = sortOrder.direction.toLowerCase() === 'asc'; + const { property, direction } = + SORT_ORDER_OPTIONS[this.domainObject.configuration.sortOrderIndex]; this.defaultSort = { property, - defaultDirection: direction + defaultDirection: direction.toLowerCase() === 'asc' }; }, sortItems(activities) { - let sortedItems = _.sortBy(activities, this.defaultSort.property); - if (!this.defaultSort.defaultDirection) { - sortedItems = sortedItems.reverse(); - } - return sortedItems; + const sortedItems = _.sortBy(activities, this.defaultSort.property); + return this.defaultSort.defaultDirection ? sortedItems : sortedItems.reverse(); }, setStatus(status) { this.status = status; @@ -548,10 +541,7 @@ export default { this.isEditing = isEditing; this.setViewFromConfig(this.domainObject.configuration); }, - sort(data) { - const property = data.property; - const direction = data.direction; - + sort({ property, direction }) { if (this.defaultSort.property === property) { this.defaultSort.defaultDirection = !this.defaultSort.defaultDirection; } else { @@ -565,10 +555,10 @@ export default { this.openmct.selection.select( [ { - element: element, + element, context: { type: 'activity', - activity: activity + activity } }, { @@ -581,6 +571,11 @@ export default { ], multiSelect ); + }, + getSortDirection(headerItem) { + return this.defaultSort.property === headerItem.property + ? this.defaultSort.defaultDirection + : headerItem.defaultDirection; } } };