Compare commits

...

12 Commits

Author SHA1 Message Date
John Hill
349be42275 add draggable true to tree items 2024-03-13 16:16:56 -07:00
John Hill
6edb3b2dc4 Update plot aria 2024-03-13 16:16:43 -07:00
John Hill
444c9ff33e update notebook snapshot drop area 2024-03-13 16:16:20 -07:00
John Hill
b7b7bc2d41 update gauge component 2024-03-13 16:16:06 -07:00
John Hill
6e1272dfe9 start to factor out bad locators 2024-03-13 16:15:34 -07:00
John Hill
d8df0e15e8 driveby 2024-03-13 16:15:17 -07:00
John Hill
a393bfb87a update component tests to match linting rules 2024-03-12 11:24:45 -07:00
John Hill
8933baf103 change ruleset 2024-03-12 11:24:14 -07:00
John Hill
82326dc658 update package 2024-03-12 11:24:02 -07:00
John Hill
df2d9ab133 Merge branch 'master' of https://github.com/nasa/openmct into eslint_update 2024-03-11 18:44:06 -07:00
John Hill
2897ca65b3 first pass of lint fixes 2024-02-26 08:59:20 -08:00
John Hill
0590b50d59 update eslint package 2024-02-26 04:28:24 -08:00
26 changed files with 179 additions and 149 deletions

View File

@@ -4,7 +4,8 @@ module.exports = {
browser: true,
es6: true,
jasmine: true,
amd: true
amd: true,
node: true
},
globals: {
_: 'readonly'
@@ -150,6 +151,7 @@ module.exports = {
'error',
{
cases: {
camelCase: true,
pascalCase: true
},
ignore: ['^.*\\.js$']

View File

@@ -1,14 +1,20 @@
/* eslint-disable no-undef */
module.exports = {
extends: ['plugin:playwright/playwright-test'],
extends: ['plugin:playwright/recommended'],
rules: {
'playwright/max-nested-describe': ['error', { max: 1 }]
},
overrides: [
{
files: ['tests/visual/*.spec.js'],
files: ['**/*.visual.spec.js'],
rules: {
'playwright/no-wait-for-timeout': 'off'
'playwright/expect-expect': 'off'
}
},
{
files: ['**/*.perf.spec.js'],
rules: {
'playwright/expect-expect': 'off'
}
}
]

View File

@@ -383,7 +383,7 @@ By adhering to this principle, we can create tests that are both robust and refl
1. Avoid creating locator aliases. This likely means that you're compensating for a bad locator. Improve the application instead.
1. Leverage `await page.goto('./', { waitUntil: 'domcontentloaded' });` instead of `{ waitUntil: 'networkidle' }`. Tests run against deployments with websockets often have issues with the networkidle detection.
#### How to make tests faster and more resilient
#### How to make tests faster and more resilient to application changes
1. Avoid app interaction when possible. The best way of doing this is to navigate directly by URL:
```js
@@ -398,6 +398,16 @@ By adhering to this principle, we can create tests that are both robust and refl
- Initial navigation should _almost_ always use the `{ waitUntil: 'domcontentloaded' }` option.
1. Avoid repeated setup to test a single assertion. Write longer tests with multiple soft assertions.
This ensures that your changes will be picked up with large refactors.
1. Use [user-facing locators](https://playwright.dev/docs/best-practices#use-locators) (Now a eslint rule!)
```js
page.getByRole('button', { name: 'Create' } )
```
Instead of
```js
page.locator('.c-create-button')
```
Note: `page.locator()` can be used in performance tests as xk6-browser does not yet support the new `page.getBy` pattern and css lookups can be [1.5x faster](https://serpapi.com/blog/css-selectors-faster-than-getbyrole-playwright/)
##### Utilizing LocalStorage
1. In order to save test runtime in the case of tests that require a decent amount of initial setup (such as in the case of testing complex displays), you may use [Playwright's `storageState` feature](https://playwright.dev/docs/api/class-browsercontext#browser-context-storage-state) to generate and load localStorage states.

View File

@@ -41,7 +41,7 @@ test.describe('CouchDB Status Indicator with mocked responses @couchdb', () => {
//Go to baseURL
await page.goto('./#/browse/mine?hideTree=true&hideInspector=true', {
waitUntil: 'networkidle'
waitUntil: 'domcontentloaded'
});
await expect(page.locator('div:has-text("CouchDB is connected")').nth(3)).toBeVisible();
});
@@ -56,7 +56,7 @@ test.describe('CouchDB Status Indicator with mocked responses @couchdb', () => {
//Go to baseURL
await page.goto('./#/browse/mine?hideTree=true&hideInspector=true', {
waitUntil: 'networkidle'
waitUntil: 'domcontentloaded'
});
await expect(page.locator('div:has-text("CouchDB is offline")').nth(3)).toBeVisible();
});
@@ -71,7 +71,7 @@ test.describe('CouchDB Status Indicator with mocked responses @couchdb', () => {
//Go to baseURL
await page.goto('./#/browse/mine?hideTree=true&hideInspector=true', {
waitUntil: 'networkidle'
waitUntil: 'domcontentloaded'
});
await expect(page.locator('div:has-text("CouchDB connectivity unknown")').nth(3)).toBeVisible();
});

View File

@@ -188,8 +188,8 @@ test.describe('Persistence operations @couchdb', () => {
// Both pages: Go to baseURL
await Promise.all([
page.goto('./', { waitUntil: 'networkidle' }),
page2.goto('./', { waitUntil: 'networkidle' })
page.goto('./', { waitUntil: 'domcontentloaded' }),
page2.goto('./', { waitUntil: 'domcontentloaded' })
]);
//Slow down the test a bit

View File

@@ -76,7 +76,7 @@ test.describe.serial('Condition Set CRUD Operations on @localStorage @2p', () =>
description: 'https://github.com/nasa/openmct/issues/7421'
});
//Navigate to baseURL with injected localStorage
await page.goto(conditionSetUrl, { waitUntil: 'networkidle' });
await page.goto(conditionSetUrl, { waitUntil: 'domcontentloaded' });
//Assertions on loaded Condition Set in main view. This is a stateful transition step after page.goto()
await expect
@@ -87,7 +87,7 @@ test.describe.serial('Condition Set CRUD Operations on @localStorage @2p', () =>
expect.soft(page.locator('_vue=item.name=Unnamed Condition Set')).toBeTruthy();
//Reload Page
await Promise.all([page.reload(), page.waitForLoadState('networkidle')]);
await Promise.all([page.reload(), page.waitForLoadState('domcontentloaded')]);
//Re-verify after reload
await expect
@@ -100,7 +100,7 @@ test.describe.serial('Condition Set CRUD Operations on @localStorage @2p', () =>
test('condition set object can be modified on @localStorage', async ({ page, openmctConfig }) => {
const { myItemsFolderName } = openmctConfig;
await page.goto(conditionSetUrl, { waitUntil: 'networkidle' });
await page.goto(conditionSetUrl, { waitUntil: 'domcontentloaded' });
//Assertions on loaded Condition Set in main view. This is a stateful transition step after page.goto()
await expect
@@ -151,7 +151,7 @@ test.describe.serial('Condition Set CRUD Operations on @localStorage @2p', () =>
expect(page.locator('a:has-text("Renamed Condition Set")')).toBeTruthy();
//Reload Page
await Promise.all([page.reload(), page.waitForLoadState('networkidle')]);
await Promise.all([page.reload(), page.waitForLoadState('domcontentloaded')]);
//Verify Main section reflects updated Name Property
await expect
@@ -213,7 +213,7 @@ test.describe.serial('Condition Set CRUD Operations on @localStorage @2p', () =>
//Feature?
//Domain Object is still available by direct URL after delete
await page.goto(conditionSetUrl, { waitUntil: 'networkidle' });
await page.goto(conditionSetUrl, { waitUntil: 'domcontentloaded' });
await expect(page.locator('.l-browse-bar__object-name')).toContainText('Unnamed Condition Set');
});
});

View File

@@ -519,7 +519,7 @@ test.describe('Display Layout', () => {
await page.reload();
// wait for annotations requests to be batched and requested
await page.waitForLoadState('networkidle');
await page.waitForLoadState('domcontentloaded');
// Network requests for the composite telemetry with multiple items should be:
// 1. a single batched request for annotations
expect(networkRequests.length).toBe(1);
@@ -531,7 +531,7 @@ test.describe('Display Layout', () => {
await page.reload();
// wait for annotations to not load (if we have any, we've got a problem)
await page.waitForLoadState('networkidle');
await page.waitForLoadState('domcontentloaded');
// In real time mode, we don't fetch annotations at all
expect(networkRequests.length).toBe(0);

View File

@@ -531,7 +531,7 @@ test.describe('Example Imagery in Flexible layout', () => {
// Click text=OK
await Promise.all([
page.waitForNavigation({ waitUntil: 'networkidle' }),
page.waitForNavigation({ waitUntil: 'domcontentloaded' }),
page.click('button:has-text("OK")'),
//Wait for Save Banner to appear
page.waitForSelector('.c-message-banner__message')
@@ -575,7 +575,7 @@ test.describe('Example Imagery in Tabs View', () => {
// Click text=OK
await Promise.all([
page.waitForNavigation({ waitUntil: 'networkidle' }),
page.waitForNavigation({ waitUntil: 'domcontentloaded' }),
page.click('button:has-text("OK")'),
//Wait for Save Banner to appear
page.waitForSelector('.c-message-banner__message')
@@ -1023,7 +1023,7 @@ async function createImageryView(page) {
// Click text=OK
await Promise.all([
page.waitForNavigation({ waitUntil: 'networkidle' }),
page.waitForNavigation({ waitUntil: 'domcontentloaded' }),
page.click('button:has-text("OK")'),
//Wait for Save Banner to appear
page.waitForSelector('.c-message-banner__message')

View File

@@ -152,7 +152,7 @@ test.describe('ExportAsJSON Disabled Actions', () => {
test.describe('ExportAsJSON ProgressBar @couchdb', () => {
let folder;
test.beforeEach(async ({ page }) => {
await page.goto('./', { waitUntil: 'networkidle' });
await page.goto('./', { waitUntil: 'domcontentloaded' });
// Perform actions to create the domain object
folder = await createDomainObjectWithDefaults(page, {
type: 'Folder'

View File

@@ -37,7 +37,7 @@ test.describe('Notebook Tests with CouchDB @couchdb', () => {
// Create Notebook
testNotebook = await createDomainObjectWithDefaults(page, { type: 'Notebook' });
await page.goto(testNotebook.url, { waitUntil: 'networkidle' });
await page.goto(testNotebook.url, { waitUntil: 'domcontentloaded' });
});
test('Inspect Notebook Entry Network Requests', async ({ page }) => {
@@ -58,7 +58,7 @@ test.describe('Notebook Tests with CouchDB @couchdb', () => {
page.click('[aria-label="Add Page"]')
]);
// Ensures that there are no other network requests
await page.waitForLoadState('networkidle');
await page.waitForLoadState('domcontentloaded');
// Assert that only two requests are made
// Network Requests are:
@@ -77,7 +77,7 @@ test.describe('Notebook Tests with CouchDB @couchdb', () => {
// 2) The shared worker event from 👆 POST request
notebookElementsRequests = [];
await nbUtils.enterTextEntry(page, 'First Entry');
await page.waitForLoadState('networkidle');
await page.waitForLoadState('domcontentloaded');
expect(notebookElementsRequests.length).toBeLessThanOrEqual(2);
// Add some tags
@@ -141,7 +141,7 @@ test.describe('Notebook Tests with CouchDB @couchdb', () => {
// 4) The shared worker event from 👆 POST request
notebookElementsRequests = [];
await nbUtils.enterTextEntry(page, 'Fourth Entry');
page.waitForLoadState('networkidle');
page.waitForLoadState('domcontentloaded');
expect(filterNonFetchRequests(notebookElementsRequests).length).toBeLessThanOrEqual(4);
@@ -153,7 +153,7 @@ test.describe('Notebook Tests with CouchDB @couchdb', () => {
// 4) The shared worker event from 👆 POST request
notebookElementsRequests = [];
await nbUtils.enterTextEntry(page, 'Fifth Entry');
page.waitForLoadState('networkidle');
page.waitForLoadState('domcontentloaded');
expect(filterNonFetchRequests(notebookElementsRequests).length).toBeLessThanOrEqual(4);
@@ -164,7 +164,7 @@ test.describe('Notebook Tests with CouchDB @couchdb', () => {
// 4) The shared worker event from 👆 POST request
notebookElementsRequests = [];
await nbUtils.enterTextEntry(page, 'Sixth Entry');
page.waitForLoadState('networkidle');
page.waitForLoadState('domcontentloaded');
expect(filterNonFetchRequests(notebookElementsRequests).length).toBeLessThanOrEqual(4);
});
@@ -227,7 +227,7 @@ async function addTagAndAwaitNetwork(page, tagName) {
page.locator(`[aria-label="Autocomplete Options"] >> text=${tagName}`).click(),
expect(page.locator(`[aria-label="Tag"]:has-text("${tagName}")`)).toBeVisible()
]);
await page.waitForLoadState('networkidle');
await page.waitForLoadState('domcontentloaded');
}
/**
@@ -246,5 +246,5 @@ async function removeTagAndAwaitNetwork(page, tagName) {
)
]);
await expect(page.locator(`[aria-label="Tag"]:has-text("${tagName}")`)).toBeHidden();
await page.waitForLoadState('networkidle');
await page.waitForLoadState('domcontentloaded');
}

View File

@@ -59,7 +59,7 @@ test.describe('Handle missing object for plots', () => {
await page.evaluate(`window.localStorage.setItem('mct', '${JSON.stringify(parsedData)}')`);
//Reloads page and clicks on stacked plot
await Promise.all([page.reload(), page.waitForLoadState('networkidle')]);
await Promise.all([page.reload(), page.waitForLoadState('domcontentloaded')]);
//Verify Main section is there on load
await expect
@@ -92,7 +92,7 @@ async function makeStackedPlot(page, myItemsFolderName) {
await page.locator('li[role="menuitem"]:has-text("Stacked Plot")').click();
await Promise.all([
page.waitForNavigation({ waitUntil: 'networkidle' }),
page.waitForNavigation({ waitUntil: 'domcontentloaded' }),
page.locator('button:has-text("OK")').click(),
//Wait for Save Banner to appear
page.waitForSelector('.c-message-banner__message')
@@ -153,7 +153,7 @@ async function createSineWaveGenerator(page) {
await page.locator('li[role="menuitem"]:has-text("Sine Wave Generator")').click();
await Promise.all([
page.waitForNavigation({ waitUntil: 'networkidle' }),
page.waitForNavigation({ waitUntil: 'domcontentloaded' }),
page.locator('button:has-text("OK")').click(),
//Wait for Save Banner to appear
page.waitForSelector('.c-message-banner__message')

View File

@@ -30,7 +30,7 @@ import { expect, test } from '../../baseFixtures.js';
test.describe('Renaming objects', () => {
test.beforeEach(async ({ page }) => {
// Go to baseURL
await page.goto('./', { waitUntil: 'networkidle' });
await page.goto('./', { waitUntil: 'domcontentloaded' });
});
test('When renaming objects, the browse bar and various components all update', async ({

View File

@@ -48,7 +48,7 @@ test.describe('Verify tooltips', () => {
const swg2Path = 'My Items / Folder Foo / Folder Bar / SWG 2';
const swg3Path = 'My Items / Folder Foo / Folder Bar / Folder Baz / SWG 3';
test.beforeEach(async ({ page, openmctConfig }) => {
test.beforeEach(async ({ page }) => {
await page.goto('./', { waitUntil: 'domcontentloaded' });
folder1 = await createDomainObjectWithDefaults(page, {
@@ -89,7 +89,7 @@ test.describe('Verify tooltips', () => {
await expandEntireTree(page);
});
test('display correct paths for LAD tables', async ({ page, openmctConfig }) => {
test('display correct paths for LAD tables', async ({ page }) => {
// Create LAD table
await createDomainObjectWithDefaults(page, {
type: 'LAD Table',
@@ -102,21 +102,25 @@ test.describe('Verify tooltips', () => {
await page.dragAndDrop(`text=${sineWaveObject1.name}`, '.c-lad-table-wrapper');
await page.dragAndDrop(`text=${sineWaveObject2.name}`, '.c-lad-table-wrapper');
await page.dragAndDrop(`text=${sineWaveObject3.name}`, '.c-lad-table-wrapper');
await page.locator('button[title="Save"]').click();
await page.getByRole('button', { name: 'Save' }).click();
await page.getByRole('listitem', { name: 'Save and Finish Editing' }).click();
await page.keyboard.down('Control');
async function getToolTip(object) {
await page.locator('.c-create-button').hover();
await page.getByRole('cell', { name: object.name }).hover();
let tooltipText = await page.locator('.c-tooltip').textContent();
return tooltipText.replace('\n', '').trim();
}
//I'm not sure why this is necessary to hover over Create
await page.getByRole('button', { name: 'Create' }).hover();
await page.getByRole('cell', { name: sineWaveObject1.name }).hover();
await expect(page.getByRole('tooltip')).toHaveText(sineWaveObject1.path);
expect(await getToolTip(sineWaveObject1)).toBe(sineWaveObject1.path);
expect(await getToolTip(sineWaveObject2)).toBe(sineWaveObject2.path);
expect(await getToolTip(sineWaveObject3)).toBe(sineWaveObject3.path);
//I'm not sure why this is necessary to hover over Create
await page.getByRole('button', { name: 'Create' }).hover();
await page.getByRole('cell', { name: sineWaveObject2.name }).hover();
await expect(page.getByRole('tooltip')).toHaveText(sineWaveObject2.path);
//I'm not sure why this is necessary to hover over Create
await page.getByRole('button', { name: 'Create' }).hover();
await page.getByRole('cell', { name: sineWaveObject3.name }).hover();
await expect(page.getByRole('tooltip')).toHaveText(sineWaveObject3.path);
});
test('display correct paths for expanded and collapsed plot legend items', async ({ page }) => {
@@ -132,26 +136,26 @@ test.describe('Verify tooltips', () => {
await page.dragAndDrop(`text=${sineWaveObject1.name}`, '.gl-plot');
await page.dragAndDrop(`text=${sineWaveObject2.name}`, '.gl-plot');
await page.dragAndDrop(`text=${sineWaveObject3.name}`, '.gl-plot');
await page.locator('button[title="Save"]').click();
await page.getByRole('button', { name: 'Save' }).click();
await page.getByRole('listitem', { name: 'Save and Finish Editing' }).click();
await page.keyboard.down('Control');
async function getCollapsedLegendToolTip(object) {
await page.locator('.c-create-button').hover();
await page.getByRole('button', { name: 'create' }).hover();
await page
.locator('.plot-series-name', { has: page.locator(`text="${object.name} Hz"`) })
.hover();
let tooltipText = await page.locator('.c-tooltip').textContent();
let tooltipText = await page.locator('.c-tooltip').innerText();
return tooltipText.replace('\n', '').trim();
}
async function getExpandedLegendToolTip(object) {
await page.locator('.c-create-button').hover();
await page.getByRole('button', { name: 'create' }).hover();
await page
.locator('.plot-series-name', { has: page.locator(`text="${object.name}"`) })
.hover();
let tooltipText = await page.locator('.c-tooltip').textContent();
let tooltipText = await page.locator('.c-tooltip').innerText();
return tooltipText.replace('\n', '').trim();
}
@@ -181,7 +185,7 @@ test.describe('Verify tooltips', () => {
has: page.locator(`text="${object.name}"`)
})
.hover();
const tooltipText = await page.locator('.c-tooltip').textContent();
const tooltipText = await page.locator('.c-tooltip').innerText();
await page.keyboard.up('Control');
return tooltipText.replace('\n', '').trim();
}
@@ -199,7 +203,7 @@ test.describe('Verify tooltips', () => {
// Edit Overlay Plot
await page.getByLabel('Edit Object').click();
await page.dragAndDrop(`text=${sineWaveObject1.name}`, '.gl-plot');
await page.locator('button[title="Save"]').click();
await page.getByRole('button', { name: 'Save' }).click();
await page.getByRole('listitem', { name: 'Save and Finish Editing' }).click();
// Create Stacked Plot
@@ -210,7 +214,7 @@ test.describe('Verify tooltips', () => {
// Edit Stacked Plot
await page.getByLabel('Edit Object').click();
await page.dragAndDrop(`text=${sineWaveObject2.name}`, '.c-plot--stacked.holder');
await page.locator('button[title="Save"]').click();
await page.getByRole('button', { name: 'Save' }).click();
await page.getByRole('listitem', { name: 'Save and Finish Editing' }).click();
// Create Display Layout
@@ -230,13 +234,13 @@ test.describe('Verify tooltips', () => {
await page.dragAndDrop(`text=${sineWaveObject3.name}`, '.l-layout__grid-holder', {
targetPosition: { x: 500, y: 200 }
});
await page.locator('button[title="Save"]').click();
await page.getByRole('button', { name: 'Save' }).click();
await page.getByRole('listitem', { name: 'Save and Finish Editing' }).click();
await page.keyboard.down('Control');
await page.getByText('Test Overlay Plot').nth(2).hover();
let tooltipText = await page.locator('.c-tooltip').textContent();
let tooltipText = await page.locator('.c-tooltip').innerText();
tooltipText = tooltipText.replace('\n', '').trim();
expect(tooltipText).toBe('My Items / Test Overlay Plot');
@@ -244,17 +248,17 @@ test.describe('Verify tooltips', () => {
await page.locator('.c-plot-legend__view-control >> nth=0').click();
await page.keyboard.down('Control');
await page.locator('.plot-wrapper-expanded-legend .plot-series-name').first().hover();
tooltipText = await page.locator('.c-tooltip').textContent();
tooltipText = await page.locator('.c-tooltip').innerText();
tooltipText = tooltipText.replace('\n', '').trim();
expect(tooltipText).toBe(sineWaveObject1.path);
await page.getByText('Test Stacked Plot').nth(2).hover();
tooltipText = await page.locator('.c-tooltip').textContent();
tooltipText = await page.locator('.c-tooltip').innerText();
tooltipText = tooltipText.replace('\n', '').trim();
expect(tooltipText).toBe('My Items / Test Stacked Plot');
await page.getByText('SWG 3').nth(2).hover();
tooltipText = await page.locator('.c-tooltip').textContent();
tooltipText = await page.locator('.c-tooltip').innerText();
tooltipText = tooltipText.replace('\n', '').trim();
expect(sineWaveObject3.path).toBe(tooltipText);
});
@@ -268,19 +272,15 @@ test.describe('Verify tooltips', () => {
await page.dragAndDrop(`text=${sineWaveObject1.name}`, '.c-fl__container >> nth=0');
await page.dragAndDrop(`text=${sineWaveObject3.name}`, '.c-fl__container >> nth=1');
await page.locator('button[title="Save"]').click();
await page.getByRole('button', { name: 'Save' }).click();
await page.getByRole('listitem', { name: 'Save and Finish Editing' }).click();
await page.keyboard.down('Control');
await page.getByText('SWG 1').nth(2).hover();
let tooltipText = await page.locator('.c-tooltip').textContent();
tooltipText = tooltipText.replace('\n', '').trim();
expect(tooltipText).toBe(sineWaveObject1.path);
await expect(page.getByRole('tooltip')).toHaveText(sineWaveObject1.path);
await page.getByText('SWG 3').nth(2).hover();
tooltipText = await page.locator('.c-tooltip').textContent();
tooltipText = tooltipText.replace('\n', '').trim();
expect(tooltipText).toBe(sineWaveObject3.path);
await expect(page.getByRole('tooltip')).toHaveText(sineWaveObject3.path);
});
test('display correct paths when hovering over tab view labels', async ({ page }) => {
@@ -292,32 +292,25 @@ test.describe('Verify tooltips', () => {
await page.dragAndDrop(`text=${sineWaveObject1.name}`, '.c-tabs-view__tabs-holder');
await page.dragAndDrop(`text=${sineWaveObject3.name}`, '.c-tabs-view__tabs-holder');
await page.locator('button[title="Save"]').click();
await page.getByRole('button', { name: 'Save' }).click();
await page.getByRole('listitem', { name: 'Save and Finish Editing' }).click();
await page.keyboard.down('Control');
await page.getByText('SWG 1').nth(2).hover();
let tooltipText = await page.locator('.c-tooltip').textContent();
tooltipText = tooltipText.replace('\n', '').trim();
expect(tooltipText).toBe(sineWaveObject1.path);
await expect(page.getByRole('tooltip')).toHaveText(sineWaveObject1.path);
await page.getByText('SWG 3').nth(2).hover();
tooltipText = await page.locator('.c-tooltip').textContent();
tooltipText = tooltipText.replace('\n', '').trim();
expect(tooltipText).toBe(sineWaveObject3.path);
await expect(page.getByRole('tooltip')).toHaveText(sineWaveObject3.path);
});
test('display correct paths when hovering tree items', async ({ page }) => {
await page.keyboard.down('Control');
await page.getByText('SWG 1').nth(0).hover();
let tooltipText = await page.locator('.c-tooltip').textContent();
tooltipText = tooltipText.replace('\n', '').trim();
expect(tooltipText).toBe(sineWaveObject1.path);
await expect(page.getByRole('tooltip')).toHaveText(sineWaveObject1.path);
await page.getByText('SWG 3').nth(0).hover();
tooltipText = await page.locator('.c-tooltip').textContent();
tooltipText = tooltipText.replace('\n', '').trim();
expect(tooltipText).toBe(sineWaveObject3.path);
await expect(page.getByRole('tooltip')).toHaveText(sineWaveObject3.path);
});
test('display correct paths when hovering search items', async ({ page }) => {
@@ -326,9 +319,7 @@ test.describe('Verify tooltips', () => {
await page.keyboard.down('Control');
await page.locator('.c-gsearch-result__title').hover();
let tooltipText = await page.locator('.c-tooltip').textContent();
tooltipText = tooltipText.replace('\n', '').trim();
expect(tooltipText).toBe(sineWaveObject3.path);
await expect(page.getByRole('tooltip')).toHaveText(sineWaveObject3.path);
});
test('display path for source telemetry when hovering over gauge', async ({ page }) => {
@@ -338,11 +329,8 @@ test.describe('Verify tooltips', () => {
});
await page.dragAndDrop(`text=${sineWaveObject3.name}`, '.c-gauge__wrapper');
await page.keyboard.down('Control');
// eslint-disable-next-line playwright/no-force-option
await page.locator('.c-gauge.c-dial').hover({ position: { x: 0, y: 0 }, force: true });
let tooltipText = await page.locator('.c-tooltip').textContent();
tooltipText = tooltipText.replace('\n', '').trim();
expect(tooltipText).toBe(sineWaveObject3.path);
await page.getByRole('meter', { name: 'Test Gauge' }).hover({ position: { x: 0, y: 0 } });
await expect(page.getByRole('tooltip')).toHaveText(sineWaveObject3.path);
});
test('display tooltip path for notebook embeds', async ({ page }) => {
@@ -354,7 +342,7 @@ test.describe('Verify tooltips', () => {
await page.dragAndDrop(`text=${sineWaveObject3.name}`, '.c-notebook__drag-area');
await page.keyboard.down('Control');
await page.locator('.c-ne__embed').hover();
let tooltipText = await page.locator('.c-tooltip').textContent();
let tooltipText = await page.locator('.c-tooltip').innerText();
tooltipText = tooltipText.replace('\n', '').trim();
expect(tooltipText).toBe(sineWaveObject3.path);
});
@@ -381,17 +369,17 @@ test.describe('Verify tooltips', () => {
await page.dragAndDrop(`text=${sineWaveObject1.name}`, '.c-telemetry-table');
await page.dragAndDrop(`text=${sineWaveObject3.name}`, '.c-telemetry-table');
await page.locator('button[title="Save"]').click();
await page.getByRole('button', { name: 'Save' }).click();
await page.getByRole('listitem', { name: 'Save and Finish Editing' }).click();
await page.keyboard.down('Control');
await page.locator('.noselect > [title="SWG 3"]').first().hover();
let tooltipText = await page.locator('.c-tooltip').textContent();
let tooltipText = await page.locator('.c-tooltip').innerText();
tooltipText = tooltipText.replace('\n', '').trim();
expect(tooltipText).toBe(sineWaveObject3.path);
await page.locator('.noselect > [title="SWG 1"]').first().hover();
tooltipText = await page.locator('.c-tooltip').textContent();
tooltipText = await page.locator('.c-tooltip').innerText();
tooltipText = tooltipText.replace('\n', '').trim();
expect(tooltipText).toBe(sineWaveObject1.path);
});
@@ -410,17 +398,17 @@ test.describe('Verify tooltips', () => {
await page.keyboard.down('Control');
await page.getByLabel('Recent Objects').getByText(sineWaveObject3.name).hover();
let tooltipText = await page.locator('.c-tooltip').textContent();
let tooltipText = await page.locator('.c-tooltip').innerText();
tooltipText = tooltipText.replace('\n', '').trim();
expect(tooltipText).toBe(sineWaveObject3.path);
await page.getByLabel('Recent Objects').getByText(sineWaveObject2.name).hover();
tooltipText = await page.locator('.c-tooltip').textContent();
tooltipText = await page.locator('.c-tooltip').innerText();
tooltipText = tooltipText.replace('\n', '').trim();
expect(tooltipText).toBe(sineWaveObject2.path);
await page.getByLabel('Recent Objects').getByText(sineWaveObject1.name).hover();
tooltipText = await page.locator('.c-tooltip').textContent();
tooltipText = await page.locator('.c-tooltip').innerText();
tooltipText = tooltipText.replace('\n', '').trim();
expect(tooltipText).toBe(sineWaveObject1.path);
});
@@ -445,22 +433,22 @@ test.describe('Verify tooltips', () => {
`text=${sineWaveObject3.name}`,
'.c-object-view.is-object-type-time-strip'
);
await page.locator('button[title="Save"]').click();
await page.getByRole('button', { name: 'Save' }).click();
await page.getByRole('listitem', { name: 'Save and Finish Editing' }).click();
await page.keyboard.down('Control');
await page.getByText(sineWaveObject1.name).nth(2).hover();
let tooltipText = await page.locator('.c-tooltip').textContent();
let tooltipText = await page.locator('.c-tooltip').innerText();
tooltipText = tooltipText.replace('\n', '').trim();
expect(tooltipText).toBe(sineWaveObject1.path);
await page.getByText(sineWaveObject2.name).nth(2).hover();
tooltipText = await page.locator('.c-tooltip').textContent();
tooltipText = await page.locator('.c-tooltip').innerText();
tooltipText = tooltipText.replace('\n', '').trim();
expect(tooltipText).toBe(sineWaveObject2.path);
await page.getByText(sineWaveObject3.name).nth(2).hover();
tooltipText = await page.locator('.c-tooltip').textContent();
tooltipText = await page.locator('.c-tooltip').innerText();
tooltipText = tooltipText.replace('\n', '').trim();
expect(tooltipText).toBe(sineWaveObject3.path);
});

View File

@@ -48,7 +48,9 @@ test.describe('Main Tree', () => {
});
await expandTreePaneItemByName(page, folder.name);
await assertTreeItemIsVisible(page, clock.name);
await expect(
page.getByRole('tree', { name: 'Main Tree' }).getByRole('treeitem', { name: clock.name })
).toBeVisible();
});
test('Creating a child object on one tab and expanding its parent on the other shows the correct composition @2p', async ({
@@ -65,8 +67,8 @@ test.describe('Main Tree', () => {
// Both pages: Go to baseURL
await Promise.all([
page.goto('./', { waitUntil: 'networkidle' }),
page2.goto('./', { waitUntil: 'networkidle' })
page.goto('./', { waitUntil: 'domcontentloaded' }),
page2.goto('./', { waitUntil: 'domcontentloaded' })
]);
const page1Folder = await createDomainObjectWithDefaults(page, {
@@ -74,7 +76,11 @@ test.describe('Main Tree', () => {
});
await expandTreePaneItemByName(page2, myItemsFolderName);
await assertTreeItemIsVisible(page2, page1Folder.name);
await expect(
page2
.getByRole('tree', { name: 'Main Tree' })
.getByRole('treeitem', { name: page1Folder.name })
).toBeVisible();
});
test('Creating a child object on one tab and expanding its parent on the other shows the correct composition @couchdb @2p', async ({
@@ -91,8 +97,8 @@ test.describe('Main Tree', () => {
// Both pages: Go to baseURL
await Promise.all([
page.goto('./', { waitUntil: 'networkidle' }),
page2.goto('./', { waitUntil: 'networkidle' })
page.goto('./', { waitUntil: 'domcontentloaded' }),
page2.goto('./', { waitUntil: 'domcontentloaded' })
]);
const page1Folder = await createDomainObjectWithDefaults(page, {
@@ -100,9 +106,13 @@ test.describe('Main Tree', () => {
});
await expandTreePaneItemByName(page2, myItemsFolderName);
await assertTreeItemIsVisible(page2, page1Folder.name);
await expect(
page2
.getByRole('tree', { name: 'Main Tree' })
.getByRole('treeitem', { name: page1Folder.name })
).toBeVisible();
});
// eslint-disable-next-line playwright/expect-expect
test('Renaming an object reorders the tree @unstable', async ({ page, openmctConfig }) => {
const { myItemsFolderName } = openmctConfig;
@@ -221,17 +231,6 @@ async function getAndAssertTreeItems(page, expected) {
expect(allTexts).toEqual(expected);
}
async function assertTreeItemIsVisible(page, name) {
const mainTree = page.getByRole('tree', {
name: 'Main Tree'
});
const treeItem = mainTree.getByRole('treeitem', {
name
});
await expect(treeItem).toBeVisible();
}
/**
* @param {import('@playwright/test').Page} page
* @param {string} name

View File

@@ -82,14 +82,14 @@ test.describe('Smoke tests for @mobile', () => {
await page.getByTitle('Collapse Browse Pane').click();
await expect(page.getByRole('main').getByText('Parent Display Layout')).toBeVisible();
//Verify both objects are in view
await expect(await page.getByLabel('Child Layout 1 Layout')).toBeVisible();
await expect(await page.getByLabel('Child Layout 2 Layout')).toBeVisible();
await expect(page.getByLabel('Child Layout 1 Layout')).toBeVisible();
await expect(page.getByLabel('Child Layout 2 Layout')).toBeVisible();
//Remove First Object to bring up confirmation dialog
await page.getByLabel('View menu items').nth(1).click();
await page.getByLabel('Remove').click();
await page.getByRole('button', { name: 'OK' }).click();
//Verify that the object is removed
await expect(await page.getByLabel('Child Layout 1 Layout')).toBeVisible();
await expect(page.getByLabel('Child Layout 1 Layout')).toBeVisible();
expect(await page.getByLabel('Child Layout 2 Layout').count()).toBe(0);
});
});

View File

@@ -39,7 +39,7 @@ const filePath = 'e2e/test-data/PerformanceDisplayLayout.json';
test.describe('Performance tests', () => {
test.beforeEach(async ({ page, browser }, testInfo) => {
// Go to baseURL
await page.goto('./', { waitUntil: 'networkidle' });
await page.goto('./', { waitUntil: 'domcontentloaded' });
// Click a:has-text("My Items")
await page.locator('a:has-text("My Items")').click({
@@ -129,12 +129,12 @@ test.describe('Performance tests', () => {
]);
//Time to Example Imagery Frame loads within Display Layout
await page.waitForSelector('.c-imagery__main-image__bg', { state: 'visible' });
await page.locator('.c-imagery__main-image__bg').waitFor({ state: 'visible' });
//Time to Example Imagery object loads
await page.waitForSelector('.c-imagery__main-image__background-image', { state: 'visible' });
await page.locator('.c-imagery__main-image__background-image').waitFor({ state: 'visible' });
//Get background-image url from background-image css prop
const backgroundImage = await page.locator('.c-imagery__main-image__background-image');
const backgroundImage = page.locator('.c-imagery__main-image__background-image');
let backgroundImageUrl = await backgroundImage.evaluate((el) => {
return window
.getComputedStyle(el)
@@ -156,15 +156,15 @@ test.describe('Performance tests', () => {
await page.evaluate(() => window.performance.mark('viewLarge.start.test')); //This is a mark only to compare evaluate timing
//Time to Imagery Rendered in Large Frame
await page.waitForSelector('.c-imagery__main-image__bg', { state: 'visible' });
await page.locator('.c-imagery__main-image__bg').waitFor({ state: 'visible' });
await page.evaluate(() => window.performance.mark('background-image-frame'));
//Time to Example Imagery object loads
await page.waitForSelector('.c-imagery__main-image__background-image', { state: 'visible' });
await page.locator('.c-imagery__main-image__background-image').waitFor({ state: 'visible' });
await page.evaluate(() => window.performance.mark('background-image-visible'));
// Get Current number of images in thumbstrip
await page.waitForSelector('.c-imagery__thumb');
await page.locator('.c-imagery__thumb').waitFor({ state: 'visible' });
const thumbCount = await page.locator('.c-imagery__thumb').count();
console.log('number of thumbs rendered ' + thumbCount);
await page.locator('.c-imagery__thumb').last().click();

View File

@@ -38,7 +38,7 @@ const notebookFilePath = 'e2e/test-data/PerformanceNotebook.json';
test.describe('Performance tests', () => {
test.beforeEach(async ({ page, browser }, testInfo) => {
// Go to baseURL
await page.goto('./', { waitUntil: 'networkidle' });
await page.goto('./', { waitUntil: 'domcontentloaded' });
// Click a:has-text("My Items")
await page.locator('a:has-text("My Items")').click({
@@ -110,20 +110,19 @@ test.describe('Performance tests', () => {
await page.evaluate(() => window.performance.mark('search-entered'));
//Search Result Appears and is clicked
await Promise.all([
page.waitForNavigation(),
page.locator('a:has-text("Performance Notebook")').first().click(),
page.evaluate(() => window.performance.mark('click-search-result'))
]);
await page.waitForSelector('.c-tree__item c-tree-and-search__loading loading', {
state: 'hidden'
});
await page
.locator('.c-tree__item c-tree-and-search__loading loading')
.waitFor({ state: 'hidden' });
await page.evaluate(() => window.performance.mark('search-spinner-gone'));
await page.waitForSelector('.l-browse-bar__object-name', { state: 'visible' });
await page.locator('.l-browse-bar__object-name').waitFor({ state: 'visible' });
await page.evaluate(() => window.performance.mark('object-title-appears'));
await page.waitForSelector('.c-notebook__entry >> nth=0', { state: 'visible' });
await page.locator('.c-notebook__entry >> nth=0').waitFor({ state: 'visible' });
await page.evaluate(() => window.performance.mark('notebook-entry-appears'));
// Click Add new Notebook Entry
@@ -139,9 +138,9 @@ test.describe('Performance tests', () => {
await page.evaluate(() => window.performance.mark('notebook-search-start'));
await page.locator('.c-notebook__search >> input').fill('Existing Entry');
await page.evaluate(() => window.performance.mark('notebook-search-filled'));
await page.waitForSelector('text=Search Results (3)', { state: 'visible' });
await page.locator('text=Search Results (3)').waitFor({ state: 'visible' });
await page.evaluate(() => window.performance.mark('notebook-search-processed'));
await page.waitForSelector('.c-notebook__entry >> nth=2', { state: 'visible' });
await page.locator('.c-notebook__entry >> nth=2').waitFor({ state: 'visible' });
await page.evaluate(() => window.performance.mark('notebook-search-processed'));
//Clear Search
@@ -154,7 +153,7 @@ test.describe('Performance tests', () => {
await page.locator('div.c-ne__time-and-content').last().hover();
await page.locator('button[title="Delete this entry"]').last().click();
await page.locator('button:has-text("Ok")').click();
await page.waitForSelector('.c-notebook__entry >> nth=3', { state: 'detached' });
await page.locator('.c-notebook__entry >> nth=3').waitFor({ state: 'detached' });
await page.evaluate(() => window.performance.mark('new-notebook-entry-deleted'));
//await client.send('HeapProfiler.enable');

View File

@@ -228,9 +228,7 @@ test.describe('Navigation memory leak is not detected in', () => {
expect(result).toBe(true);
});
test('display layout with plots of swgs, alphanumerics, and condition sets, ', async ({
page
}) => {
test('display layout with plots of swgs, alphanumerics, and condition sets', async ({ page }) => {
const result = await navigateToObjectAndDetectMemoryLeak(
page,
'display-layout-simple-telemetry'

View File

@@ -78,7 +78,7 @@ test.describe('Tabs View', () => {
await page.getByLabel(`${sineWaveGenerator.name} tab`, { exact: true }).click();
// ensure sine wave generator visible
expect(await page.locator('.c-plot').isVisible()).toBe(true);
await expect(page.locator('.c-plot')).toBeVisible();
// now select notebook and clear animation calls
await page.getByLabel(`${notebook.name} tab`, { exact: true }).click();

View File

@@ -84,7 +84,7 @@ test.describe('Plot Tagging Performance', () => {
await setRealTimeMode(page);
// Search for Science
await page.getByRole('searchbox', { name: 'Search Input' });
await page.getByRole('searchbox', { name: 'Search Input' }).click();
await page.getByRole('searchbox', { name: 'Search Input' }).fill('sc');
// click on the search result

View File

@@ -34,7 +34,7 @@
"eslint-config-prettier": "9.1.0",
"eslint-plugin-compat": "4.2.0",
"eslint-plugin-no-unsanitized": "4.0.2",
"eslint-plugin-playwright": "0.12.0",
"eslint-plugin-playwright": "1.5.2",
"eslint-plugin-prettier": "5.1.3",
"eslint-plugin-simple-import-sort": "10.0.0",
"eslint-plugin-unicorn": "49.0.0",

View File

@@ -20,7 +20,13 @@ this source code distribution or the Licensing information page available
at runtime from the About dialog for additional information.
-->
<template>
<div ref="tooltip-wrapper" class="c-menu c-tooltip-wrapper" :style="toolTipLocationStyle">
<div
ref="tooltip-wrapper"
class="c-menu c-tooltip-wrapper"
:style="toolTipLocationStyle"
role="tooltip"
aria-live="polite"
>
<div class="c-tooltip">
{{ toolTipText }}
</div>

View File

@@ -24,8 +24,13 @@
ref="gaugeWrapper"
class="c-gauge__wrapper js-gauge-wrapper"
:class="gaugeClasses"
:aria-label="gaugeTitle"
:title="gaugeTitle"
:aria-label="`${domainObject.name}`"
role="meter"
:aria-valuemin="rangeLow"
:aria-valuemax="rangeHigh"
:aria-valuenow="curVal"
:aria-valuetext="`Current value: ${curVal}`"
>
<template v-if="typeDial">
<svg

View File

@@ -98,12 +98,14 @@
v-if="selectedPage && !selectedPage.isLocked"
:aria-disabled="activeTransaction"
class="c-notebook__drag-area icon-plus"
aria-dropeffect="link"
aria-labelledby="newEntryLabel"
@click="newEntry(null, $event)"
@dragover="dragOver"
@drop.capture="dropCapture"
@drop="dropOnEntry($event)"
>
<span class="c-notebook__drag-area__label">
<span id="newEntryLabel" class="c-notebook__drag-area__label">
To start a new entry, click here or drag and drop any object
</span>
</div>
@@ -150,9 +152,10 @@
<button
class="c-button commit-button icon-lock"
title="Commit entries and lock this page from further changes"
aria-labelledby="commitEntriesLabel"
@click="lockPage()"
>
<span class="c-button__label">Commit Entries</span>
<span id="commitEntriesLabel" class="c-button__label">Commit Entries</span>
</button>
</div>
</div>

View File

@@ -94,9 +94,15 @@
<button
class="c-button icon-minus"
title="Zoom out"
aria-label="Zoom out"
@click="zoom('out', 0.2)"
></button>
<button class="c-button icon-plus" title="Zoom in" @click="zoom('in', 0.2)"></button>
<button
class="c-button icon-plus"
title="Zoom in"
aria-label="Zoom in"
@click="zoom('in', 0.2)"
></button>
</div>
<div
v-if="plotHistory.length && !options.compact"
@@ -104,12 +110,14 @@
>
<button
class="c-button icon-arrow-left"
title="Restore previous pan/zoom"
title="Restore previous pan and zoom"
aria-label="Restore previous pan and zoom"
@click="back()"
></button>
<button
class="c-button icon-reset"
title="Reset pan/zoom"
title="Reset pan and zoom"
aria-label="Reset pan and zoom"
@click="resumeRealtimeData()"
></button>
</div>
@@ -121,12 +129,14 @@
v-if="!isFrozen"
class="c-button icon-pause"
title="Pause incoming real-time data"
aria-label="Pause incoming real-time data"
@click="pause()"
></button>
<button
v-if="isFrozen"
class="c-button icon-arrow-right pause-play is-paused"
title="Resume displaying real-time data"
aria-label="Resume displaying real-time data"
@click="resumeRealtimeData()"
></button>
</div>
@@ -134,6 +144,7 @@
<button
class="c-button icon-clock"
title="Synchronize Time Conductor"
aria-label="Synchronize Time Conductor"
@click="showSynchronizeDialog()"
></button>
</div>
@@ -142,12 +153,14 @@
class="c-button icon-crosshair"
:class="{ 'is-active': cursorGuide }"
title="Toggle cursor guides"
aria-label="Toggle cursor guides"
@click="toggleCursorGuide"
></button>
<button
class="c-button"
:class="{ 'icon-grid-on': gridLines, 'icon-grid-off': !gridLines }"
title="Toggle grid lines"
aria-label="Toggle grid lines"
@click="toggleGridLines"
></button>
</div>

View File

@@ -85,6 +85,7 @@
v-for="(treeItem, index) in visibleItems"
:key="`${treeItem.navigationPath}-${index}-${treeItem.object.name}`"
:node="treeItem"
draggable="true"
:is-selector-tree="isSelectorTree"
:selected-item="selectedItem"
:active-search="activeSearch"