diff --git a/.circleci/config.yml b/.circleci/config.yml index 33b4d81ec2..3f7988be22 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -5,7 +5,7 @@ orbs: executors: pw-focal-development: docker: - - image: mcr.microsoft.com/playwright:v1.45.0-focal + - image: mcr.microsoft.com/playwright:v1.45.2-focal environment: NODE_ENV: development # Needed to ensure 'dist' folder created and devDependencies installed PERCY_POSTINSTALL_BROWSER: 'true' # Needed to store the percy browser in cache deps @@ -159,7 +159,7 @@ jobs: steps: - build_and_install: node-version: lts/hydrogen - - run: npx playwright@1.45.0 install #Necessary for bare ubuntu machine + - run: npx playwright@1.45.2 install #Necessary for bare ubuntu machine - run: | export $(cat src/plugins/persistence/couch/.env.ci | xargs) docker-compose -f src/plugins/persistence/couch/couchdb-compose.yaml up --detach diff --git a/.github/codeql/codeql-config.yml b/.github/codeql/codeql-config.yml index 98f45f4da8..ab0dddd782 100644 --- a/.github/codeql/codeql-config.yml +++ b/.github/codeql/codeql-config.yml @@ -1 +1,5 @@ name: 'Custom CodeQL config' + +paths-ignore: + # Ignore e2e tests and framework + - e2e diff --git a/.github/workflows/e2e-couchdb.yml b/.github/workflows/e2e-couchdb.yml index 8a72a1a3db..61ccdf6274 100644 --- a/.github/workflows/e2e-couchdb.yml +++ b/.github/workflows/e2e-couchdb.yml @@ -37,7 +37,7 @@ jobs: username: ${{ secrets.DOCKERHUB_USERNAME }} password: ${{ secrets.DOCKERHUB_TOKEN }} - - run: npx playwright@1.45.0 install + - run: npx playwright@1.45.2 install - name: Start CouchDB Docker Container and Init with Setup Scripts run: | diff --git a/.github/workflows/e2e-flakefinder.yml b/.github/workflows/e2e-flakefinder.yml index 89bbb25119..4688e5d18a 100644 --- a/.github/workflows/e2e-flakefinder.yml +++ b/.github/workflows/e2e-flakefinder.yml @@ -30,7 +30,7 @@ jobs: restore-keys: | ${{ runner.os }}-node- - - run: npx playwright@1.45.0 install + - run: npx playwright@1.45.2 install - run: npm ci --no-audit --progress=false - name: Run E2E Tests (Repeated 10 Times) diff --git a/.github/workflows/e2e-perf.yml b/.github/workflows/e2e-perf.yml index 4003590d0a..27bfad23cb 100644 --- a/.github/workflows/e2e-perf.yml +++ b/.github/workflows/e2e-perf.yml @@ -28,7 +28,7 @@ jobs: restore-keys: | ${{ runner.os }}-node- - - run: npx playwright@1.45.0 install + - run: npx playwright@1.45.2 install - run: npm ci --no-audit --progress=false - run: npm run test:perf:localhost - run: npm run test:perf:contract diff --git a/.github/workflows/e2e-pr.yml b/.github/workflows/e2e-pr.yml index caf59f174b..9c0724f645 100644 --- a/.github/workflows/e2e-pr.yml +++ b/.github/workflows/e2e-pr.yml @@ -33,7 +33,7 @@ jobs: restore-keys: | ${{ runner.os }}-node- - - run: npx playwright@1.45.0 install + - run: npx playwright@1.45.2 install - run: npx playwright install chrome-beta - run: npm ci --no-audit --progress=false - run: npm run test:e2e:full -- --max-failures=40 diff --git a/e2e/package.json b/e2e/package.json index 056e57761f..3b1343d0e5 100644 --- a/e2e/package.json +++ b/e2e/package.json @@ -17,10 +17,13 @@ "@types/sinonjs__fake-timers": "8.1.5", "@percy/cli": "1.27.4", "@percy/playwright": "1.0.4", - "@playwright/test": "1.45.0", + "@playwright/test": "1.45.2", "@axe-core/playwright": "4.8.5", "sinon": "17.0.0" }, - "author": "NASA Ames Research Center", + "author": { + "name": "National Aeronautics and Space Administration", + "url": "https://www.nasa.gov" + }, "license": "Apache-2.0" } \ No newline at end of file diff --git a/e2e/tests/functional/plugins/plot/autoscale.e2e.spec.js b/e2e/tests/functional/plugins/plot/autoscale.e2e.spec.js index cb3891137f..1cd53295c3 100644 --- a/e2e/tests/functional/plugins/plot/autoscale.e2e.spec.js +++ b/e2e/tests/functional/plugins/plot/autoscale.e2e.spec.js @@ -61,11 +61,12 @@ test.describe('Autoscale', () => { await page.getByLabel('Edit Object').click(); await page.getByRole('tab', { name: 'Config' }).click(); + + // turn off autoscale await page.getByRole('checkbox', { name: 'Auto scale' }).uncheck(); - await page.getByRole('spinbutton').first().fill(-2); - // set maximum value - await page.getByRole('spinbutton').nth(1).fill(2); + await page.getByLabel('Y Axis 1 Minimum value').fill('-2'); + await page.getByLabel('Y Axis 1 Maximum value').fill('2'); // save await page.click('button[title="Save"]'); diff --git a/e2e/tests/functional/plugins/plot/overlayPlot.e2e.spec.js b/e2e/tests/functional/plugins/plot/overlayPlot.e2e.spec.js index 114d8beb79..c5a9aa6874 100644 --- a/e2e/tests/functional/plugins/plot/overlayPlot.e2e.spec.js +++ b/e2e/tests/functional/plugins/plot/overlayPlot.e2e.spec.js @@ -91,7 +91,7 @@ test.describe('Overlay Plot', () => { // Assert that the legend is collapsed by default await expect(page.getByLabel('Plot Legend Collapsed')).toBeVisible(); await expect(page.getByLabel('Plot Legend Expanded')).toBeHidden(); - await expect(page.getByLabel('Expand by Default')).toHaveText('No'); + await expect(page.getByLabel('Expand by Default')).toHaveText(/No/); expect(await page.getByLabel('Plot Legend Item').count()).toBe(3); @@ -106,7 +106,7 @@ test.describe('Overlay Plot', () => { await expect(page.getByRole('cell', { name: 'Name' })).toBeVisible(); await expect(page.getByRole('cell', { name: 'Timestamp' })).toBeVisible(); await expect(page.getByRole('cell', { name: 'Value' })).toBeVisible(); - await expect(page.getByLabel('Expand by Default')).toHaveText('Yes'); + await expect(page.getByLabel('Expand by Default')).toHaveText(/Yes/); await expect(page.getByLabel('Plot Legend Item')).toHaveCount(3); // Assert that the legend is expanded on page load @@ -116,7 +116,7 @@ test.describe('Overlay Plot', () => { await expect(page.getByRole('cell', { name: 'Name' })).toBeVisible(); await expect(page.getByRole('cell', { name: 'Timestamp' })).toBeVisible(); await expect(page.getByRole('cell', { name: 'Value' })).toBeVisible(); - await expect(page.getByLabel('Expand by Default')).toHaveText('Yes'); + await expect(page.getByLabel('Expand by Default')).toHaveText(/Yes/); await expect(page.getByLabel('Plot Legend Item')).toHaveCount(3); }); @@ -144,15 +144,8 @@ test.describe('Overlay Plot', () => { // Expand the "Sine Wave Generator" plot series options and enable limit lines await page.getByRole('tab', { name: 'Config' }).click(); - await page - .getByRole('list', { name: 'Plot Series Properties' }) - .locator('span') - .first() - .click(); - await page - .getByRole('list', { name: 'Plot Series Properties' }) - .locator('[title="Display limit lines"]~div input') - .check(); + await page.getByLabel('Expand Sine Wave Generator:').click(); + await page.getByLabel('Limit lines').check(); await assertLimitLinesExistAndAreVisible(page); @@ -215,21 +208,13 @@ test.describe('Overlay Plot', () => { // Expand the "Sine Wave Generator" plot series options and enable limit lines await page.getByRole('tab', { name: 'Config' }).click(); - await page - .getByRole('list', { name: 'Plot Series Properties' }) - .locator('span') - .first() - .click(); - await page - .getByRole('list', { name: 'Plot Series Properties' }) - .getByRole('checkbox', { name: 'Limit lines' }) - .check(); + await page.getByLabel('Expand Sine Wave Generator:').click(); + await page.getByLabel('Limit lines').check(); await assertLimitLinesExistAndAreVisible(page); - // Save (exit edit mode) - await page.locator('button[title="Save"]').click(); - await page.locator('li[title="Save and Finish Editing"]').click(); + await page.getByRole('button', { name: 'Save' }).click(); + await page.getByRole('listitem', { name: 'Save and Finish Editing' }).click(); const initialCoords = await assertLimitLinesExistAndAreVisible(page); // Resize the chart container by showing the snapshot pane. diff --git a/e2e/tests/functional/plugins/plot/plotControls.e2e.spec.js b/e2e/tests/functional/plugins/plot/plotControls.e2e.spec.js index b0b07da83b..ba614cf90c 100644 --- a/e2e/tests/functional/plugins/plot/plotControls.e2e.spec.js +++ b/e2e/tests/functional/plugins/plot/plotControls.e2e.spec.js @@ -77,12 +77,12 @@ test.describe('Plot Controls', () => { await page.getByLabel('Edit Object').click(); await page.getByRole('tab', { name: 'Config' }).click(); + + // turn off autoscale await page.getByRole('checkbox', { name: 'Auto scale' }).uncheck(); - // set minimum value - await page.getByRole('spinbutton').first().fill(-1); - // set maximum value - await page.getByRole('spinbutton').nth(1).fill(1); + await page.getByLabel('Y Axis 1 Minimum value').fill('-1'); + await page.getByLabel('Y Axis 1 Maximum value').fill('1'); // save await page.click('button[title="Save"]'); diff --git a/e2e/tests/functional/plugins/plot/stackedPlot.e2e.spec.js b/e2e/tests/functional/plugins/plot/stackedPlot.e2e.spec.js index 3cffb3b28c..aed64e7e8c 100644 --- a/e2e/tests/functional/plugins/plot/stackedPlot.e2e.spec.js +++ b/e2e/tests/functional/plugins/plot/stackedPlot.e2e.spec.js @@ -39,19 +39,23 @@ test.describe('Stacked Plot', () => { await page.goto('./', { waitUntil: 'domcontentloaded' }); stackedPlot = await createDomainObjectWithDefaults(page, { - type: 'Stacked Plot' + type: 'Stacked Plot', + name: 'Stacked Plot' }); swgA = await createDomainObjectWithDefaults(page, { type: 'Sine Wave Generator', + name: 'Sine Wave Generator A', parent: stackedPlot.uuid }); swgB = await createDomainObjectWithDefaults(page, { type: 'Sine Wave Generator', + name: 'Sine Wave Generator B', parent: stackedPlot.uuid }); swgC = await createDomainObjectWithDefaults(page, { type: 'Sine Wave Generator', + name: 'Sine Wave Generator C', parent: stackedPlot.uuid }); }); @@ -151,40 +155,80 @@ test.describe('Stacked Plot', () => { await page.getByRole('tab', { name: 'Config' }).click(); // Click on the 1st plot - await page.locator(`[aria-label="Stacked Plot Item ${swgA.name}"] canvas`).nth(1).click(); + await page + .getByLabel('Stacked Plot Item Sine Wave Generator A') + .getByLabel('Plot Canvas') + .click(); // Assert that the inspector shows the Y Axis properties for swgA - await expect(page.locator('[aria-label="Plot Series Properties"] >> h2')).toContainText( - 'Plot Series' - ); + await expect(page.getByRole('heading', { name: 'Plot Series' })).toBeVisible(); await expect(page.getByRole('heading', { name: 'Y Axis' })).toBeVisible(); await expect( - page.locator('[aria-label="Plot Series Properties"] .c-object-label') - ).toContainText(swgA.name); + page.getByLabel('Inspector Views').getByText('Sine Wave Generator A', { exact: true }) + ).toBeVisible(); // Click on the 2nd plot - await page.locator(`[aria-label="Stacked Plot Item ${swgB.name}"] canvas`).nth(1).click(); - + await page + .getByLabel('Stacked Plot Item Sine Wave Generator B') + .getByLabel('Plot Canvas') + .click(); // Assert that the inspector shows the Y Axis properties for swgB - await expect(page.locator('[aria-label="Plot Series Properties"] >> h2')).toContainText( - 'Plot Series' - ); + await expect(page.getByRole('heading', { name: 'Plot Series' })).toBeVisible(); await expect(page.getByRole('heading', { name: 'Y Axis' })).toBeVisible(); await expect( - page.locator('[aria-label="Plot Series Properties"] .c-object-label') - ).toContainText(swgB.name); + page.getByLabel('Inspector Views').getByText('Sine Wave Generator B', { exact: true }) + ).toBeVisible(); // Click on the 3rd plot - await page.locator(`[aria-label="Stacked Plot Item ${swgC.name}"] canvas`).nth(1).click(); - - // Assert that the inspector shows the Y Axis properties for swgC - await expect(page.locator('[aria-label="Plot Series Properties"] >> h2')).toContainText( - 'Plot Series' - ); + await page + .getByLabel('Stacked Plot Item Sine Wave Generator C') + .getByLabel('Plot Canvas') + .click(); + // Assert that the inspector shows the Y Axis properties for swgB + await expect(page.getByRole('heading', { name: 'Plot Series' })).toBeVisible(); await expect(page.getByRole('heading', { name: 'Y Axis' })).toBeVisible(); await expect( - page.locator('[aria-label="Plot Series Properties"] .c-object-label') - ).toContainText(swgC.name); + page.getByLabel('Inspector Views').getByText('Sine Wave Generator C', { exact: true }) + ).toBeVisible(); + + // Go into edit mode + await page.getByLabel('Edit Object').click(); + + await page.getByRole('tab', { name: 'Config' }).click(); + + // Click on the 1st plot + await page.getByLabel('Stacked Plot Item Sine Wave Generator A').click(); + + // Assert that the inspector shows the Y Axis properties for swgA + await expect(page.getByRole('heading', { name: 'Plot Series' })).toBeVisible(); + await expect(page.getByRole('heading', { name: 'Y Axis' })).toBeVisible(); + await expect( + page.getByLabel('Inspector Views').getByText('Sine Wave Generator A', { exact: true }) + ).toBeVisible(); + + // Click on the 2nd plot + await page.getByLabel('Stacked Plot Item Sine Wave Generator B').click(); + + // Assert that the inspector shows the Y Axis properties for swgB + await expect(page.getByRole('heading', { name: 'Plot Series' })).toBeVisible(); + await expect(page.getByRole('heading', { name: 'Y Axis' })).toBeVisible(); + await expect( + page.getByLabel('Inspector Views').getByText('Sine Wave Generator B', { exact: true }) + ).toBeVisible(); + + // Click on the 3rd plot + await page.getByLabel('Stacked Plot Item Sine Wave Generator C').click(); + + // Assert that the inspector shows the Y Axis properties for swgC + await expect(page.getByRole('heading', { name: 'Plot Series' })).toBeVisible(); + await expect(page.getByRole('heading', { name: 'Y Axis' })).toBeVisible(); + await expect( + page.getByLabel('Inspector Views').getByText('Sine Wave Generator C', { exact: true }) + ).toBeVisible(); + }); + + test('Changing properties of an immutable child plot are applied correctly', async ({ page }) => { + await page.goto(stackedPlot.url); // Go into edit mode await page.getByLabel('Edit Object').click(); @@ -192,40 +236,35 @@ test.describe('Stacked Plot', () => { await page.getByRole('tab', { name: 'Config' }).click(); // Click on canvas for the 1st plot - await page.locator(`[aria-label="Stacked Plot Item ${swgA.name}"]`).click(); + await page.getByLabel(`Stacked Plot Item ${swgA.name}`).click(); - // Assert that the inspector shows the Y Axis properties for swgA - await expect(page.locator('[aria-label="Plot Series Properties"] >> h2')).toContainText( - 'Plot Series' - ); - await expect(page.getByRole('heading', { name: 'Y Axis' })).toBeVisible(); + // Expand config for the series + await page.getByLabel('Expand Sine Wave Generator').click(); + + // turn off alarm markers + await page.getByLabel('Alarm Markers').uncheck(); + + // save + await page.getByRole('button', { name: 'Save' }).click(); + await page.getByRole('listitem', { name: 'Save and Finish Editing' }).click(); + + // reload page and waitForPlotsToRender + await page.reload(); + await waitForPlotsToRender(page); + + // Click on canvas for the 1st plot + await page.getByLabel(`Stacked Plot Item ${swgA.name}`).click(); + + // Expand config for the series + //TODO Fix this locator + await page.getByLabel('Expand Sine Wave Generator A generator').click(); + + // Assert that alarm markers are still turned off await expect( - page.locator('[aria-label="Plot Series Properties"] .c-object-label') - ).toContainText(swgA.name); - - //Click on canvas for the 2nd plot - await page.locator(`[aria-label="Stacked Plot Item ${swgB.name}"]`).click(); - - // Assert that the inspector shows the Y Axis properties for swgB - await expect(page.locator('[aria-label="Plot Series Properties"] >> h2')).toContainText( - 'Plot Series' - ); - await expect(page.getByRole('heading', { name: 'Y Axis' })).toBeVisible(); - await expect( - page.locator('[aria-label="Plot Series Properties"] .c-object-label') - ).toContainText(swgB.name); - - //Click on canvas for the 3rd plot - await page.locator(`[aria-label="Stacked Plot Item ${swgC.name}"]`).click(); - - // Assert that the inspector shows the Y Axis properties for swgC - await expect(page.locator('[aria-label="Plot Series Properties"] >> h2')).toContainText( - 'Plot Series' - ); - await expect(page.getByRole('heading', { name: 'Y Axis' })).toBeVisible(); - await expect( - page.locator('[aria-label="Plot Series Properties"] .c-object-label') - ).toContainText(swgC.name); + page + .getByTitle('Display markers visually denoting points in alarm.') + .getByRole('cell', { name: 'Disabled' }) + ).toBeVisible(); }); test('the legend toggles between aggregate and per child', async ({ page }) => { diff --git a/e2e/tests/visual-a11y/components/timeConductor.visual.spec.js b/e2e/tests/visual-a11y/components/timeConductor.visual.spec.js index eef1114e40..bd089bbb6c 100644 --- a/e2e/tests/visual-a11y/components/timeConductor.visual.spec.js +++ b/e2e/tests/visual-a11y/components/timeConductor.visual.spec.js @@ -48,7 +48,13 @@ test.describe('Visual - Time Conductor', () => { // await scanForA11yViolations(page, testInfo.title); // }); - test('Visual - Time Conductor (Fixed time) @clock @snapshot', async ({ page }) => { + /** + * FIXME: This test fails sporadically due to layout shift during initial load. + * The layout shift seems to be caused by loading Open MCT's icons, which are not preloaded + * and load after the initial DOM content has loaded. + * @see https://github.com/nasa/openmct/issues/7775 + */ + test.fixme('Visual - Time Conductor (Fixed time) @clock @snapshot', async ({ page }) => { // Navigate to a specific view that uses the Time Conductor in Fixed Time mode with inspect and browse panes collapsed await page.goto( `./#/browse/mine?tc.mode=fixed&tc.startBound=${MISSION_TIME_FIXED_START}&tc.endBound=${MISSION_TIME_FIXED_END}&tc.timeSystem=utc&view=grid&hideInspector=true&hideTree=true`, diff --git a/package-lock.json b/package-lock.json index 8c23c496ea..0e0cffe512 100644 --- a/package-lock.json +++ b/package-lock.json @@ -104,7 +104,7 @@ "@axe-core/playwright": "4.8.5", "@percy/cli": "1.27.4", "@percy/playwright": "1.0.4", - "@playwright/test": "1.45.0", + "@playwright/test": "1.45.2", "@types/sinonjs__fake-timers": "8.1.5", "sinon": "17.0.0" } @@ -1550,12 +1550,12 @@ } }, "node_modules/@playwright/test": { - "version": "1.45.0", - "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.45.0.tgz", - "integrity": "sha512-TVYsfMlGAaxeUllNkywbwek67Ncf8FRGn8ZlRdO291OL3NjG9oMbfVhyP82HQF0CZLMrYsvesqoUekxdWuF9Qw==", + "version": "1.45.2", + "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.45.2.tgz", + "integrity": "sha512-JxG9eq92ET75EbVi3s+4sYbcG7q72ECeZNbdBlaMkGcNbiDQ4cAi8U2QP5oKkOx+1gpaiL1LDStmzCaEM1Z6fQ==", "dev": true, "dependencies": { - "playwright": "1.45.0" + "playwright": "1.45.2" }, "bin": { "playwright": "cli.js" @@ -8849,12 +8849,12 @@ } }, "node_modules/playwright": { - "version": "1.45.0", - "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.45.0.tgz", - "integrity": "sha512-4z3ac3plDfYzGB6r0Q3LF8POPR20Z8D0aXcxbJvmfMgSSq1hkcgvFRXJk9rUq5H/MJ0Ktal869hhOdI/zUTeLA==", + "version": "1.45.2", + "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.45.2.tgz", + "integrity": "sha512-ReywF2t/0teRvNBpfIgh5e4wnrI/8Su8ssdo5XsQKpjxJj+jspm00jSoz9BTg91TT0c9HRjXO7LBNVrgYj9X0g==", "dev": true, "dependencies": { - "playwright-core": "1.45.0" + "playwright-core": "1.45.2" }, "bin": { "playwright": "cli.js" @@ -8867,9 +8867,9 @@ } }, "node_modules/playwright-core": { - "version": "1.45.0", - "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.45.0.tgz", - "integrity": "sha512-lZmHlFQ0VYSpAs43dRq1/nJ9G/6SiTI7VPqidld9TDefL9tX87bTKExWZZUF5PeRyqtXqd8fQi2qmfIedkwsNQ==", + "version": "1.45.2", + "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.45.2.tgz", + "integrity": "sha512-ha175tAWb0dTK0X4orvBIqi3jGEt701SMxMhyujxNrgd8K0Uy5wMSwwcQHtyB4om7INUkfndx02XnQ2p6dvLDw==", "dev": true, "bin": { "playwright-core": "cli.js" diff --git a/src/plugins/charts/bar/inspector/SeriesOptions.vue b/src/plugins/charts/bar/inspector/SeriesOptions.vue index 06b02d52b8..eae12be23b 100644 --- a/src/plugins/charts/bar/inspector/SeriesOptions.vue +++ b/src/plugins/charts/bar/inspector/SeriesOptions.vue @@ -21,7 +21,7 @@ -->