diff --git a/e2e/appActions.js b/e2e/appActions.js index c8c27934fd..c3edf98242 100644 --- a/e2e/appActions.js +++ b/e2e/appActions.js @@ -275,6 +275,17 @@ async function navigateToObjectWithFixedTimeBounds(page, url, start, end) { ); } +/** + * Navigates directly to a given object url, in real-time mode. + * @param {import('@playwright/test').Page} page + * @param {string} url The url to the domainObject + */ +async function navigateToObjectWithRealTime(page, url, start = '1800000', end = '30000') { + await page.goto( + `${url}?tc.mode=local&tc.startDelta=${start}&tc.endDelta=${end}&tc.timeSystem=utc` + ); +} + /** * Open the given `domainObject`'s context menu from the object tree. * Expands the path to the object and scrolls to it if necessary. @@ -656,6 +667,7 @@ export { getFocusedObjectUuid, getHashUrlToDomainObject, navigateToObjectWithFixedTimeBounds, + navigateToObjectWithRealTime, openObjectTreeContextMenu, renameObjectFromContextMenu, setEndOffset, diff --git a/e2e/tests/functional/plugins/telemetryTable/telemetryTable.e2e.spec.js b/e2e/tests/functional/plugins/telemetryTable/telemetryTable.e2e.spec.js index f46dbd48fd..688bf50c84 100644 --- a/e2e/tests/functional/plugins/telemetryTable/telemetryTable.e2e.spec.js +++ b/e2e/tests/functional/plugins/telemetryTable/telemetryTable.e2e.spec.js @@ -22,8 +22,8 @@ import { createDomainObjectWithDefaults, - setTimeConductorBounds, - setTimeConductorMode + navigateToObjectWithRealTime, + setTimeConductorBounds } from '../../../../appActions.js'; import { expect, test } from '../../../../pluginFixtures.js'; @@ -39,12 +39,52 @@ test.describe('Telemetry Table', () => { type: 'Sine Wave Generator', parent: table.uuid }); - await page.goto(table.url); - await setTimeConductorMode(page, false); + await navigateToObjectWithRealTime(page, table.url); const rows = page.getByLabel('table content').getByLabel('Table Row'); await expect(rows).toHaveCount(50); }); + test('on load, auto scrolls to top for descending, and to bottom for ascending', async ({ + page + }) => { + const sineWaveGenerator = await createDomainObjectWithDefaults(page, { + type: 'Sine Wave Generator', + parent: table.uuid + }); + + // verify in telemetry table object view + await navigateToObjectWithRealTime(page, table.url); + + expect(await getScrollPosition(page)).toBe(0); + + // verify in telemetry table view + await page.goto(sineWaveGenerator.url); + await page.getByLabel('Open the View Switcher Menu').click(); + await page.getByText('Telemetry Table', { exact: true }).click(); + + expect(await getScrollPosition(page)).toBe(0); + + // navigate back to table + await page.goto(table.url); + + // go into edit mode + await page.getByLabel('Edit Object').click(); + + // change sort direction + await page.locator('thead div').filter({ hasText: 'Time' }).click(); + + // save view + await page.getByRole('button', { name: 'Save' }).click(); + await page.getByRole('listitem', { name: 'Save and Finish Editing' }).click(); + + // navigate away and back + await page.goto(sineWaveGenerator.url); + await page.goto(table.url); + + // verify scroll position + expect(await getScrollPosition(page, false)).toBeLessThan(1); + }); + test('unpauses and filters data when paused by button and user changes bounds', async ({ page }) => { @@ -183,3 +223,42 @@ test.describe('Telemetry Table', () => { await page.click('button[title="Pause"]'); }); }); + +async function getScrollPosition(page, top = true) { + const tableBody = page.locator('.c-table__body-w'); + + // Wait for the scrollbar to appear + await tableBody.evaluate((node) => { + return new Promise((resolve) => { + function checkScroll() { + if (node.scrollHeight > node.clientHeight) { + resolve(); + } else { + setTimeout(checkScroll, 100); + } + } + checkScroll(); + }); + }); + + // make sure there are rows + const rows = page.getByLabel('table content').getByLabel('Table Row'); + await rows.first().waitFor(); + + // Using this to allow for rows to come and go, so we can truly test the scroll position + // eslint-disable-next-line playwright/no-wait-for-timeout + await page.waitForTimeout(1000); + + const { scrollTop, clientHeight, scrollHeight } = await tableBody.evaluate((node) => ({ + scrollTop: node.scrollTop, + clientHeight: node.clientHeight, + scrollHeight: node.scrollHeight + })); + + // eslint-disable-next-line playwright/no-conditional-in-test + if (top) { + return scrollTop; + } else { + return Math.abs(scrollHeight - (scrollTop + clientHeight)); + } +} diff --git a/src/plugins/telemetryTable/components/TableComponent.vue b/src/plugins/telemetryTable/components/TableComponent.vue index 9b6f9883e5..4a735e3598 100644 --- a/src/plugins/telemetryTable/components/TableComponent.vue +++ b/src/plugins/telemetryTable/components/TableComponent.vue @@ -561,6 +561,9 @@ export default { this.table.initialize(); this.rescaleToContainer(); + + // Scroll to the top of the table after loading + this.addToAfterLoadActions(this.scroll); }, beforeUnmount() { this.table.off('object-added', this.addObject);