Light refactor of visual tests (#5585)

This commit is contained in:
John Hill
2023-08-11 17:18:08 -07:00
committed by GitHub
parent 10f3e13e4d
commit 50694f600c
31 changed files with 935 additions and 541 deletions

View File

@@ -117,6 +117,35 @@ test.describe('Renaming Timer Object', () => {
});
});
/**
* The next most important concept in our testing is working with telemetry objects. Telemetry is at the core of Open MCT
* and we have developed a great pattern for working with it.
*/
test.describe('Advanced: Working with telemetry objects', () => {
let displayLayout;
test.beforeEach(async ({ page }) => {
await page.goto('./', { waitUntil: 'domcontentloaded' });
// Create a Display Layout with a meaningful name
displayLayout = await createDomainObjectWithDefaults(page, {
type: 'Display Layout',
name: 'Display Layout with Embedded SWG'
});
// Create Telemetry object within the parent object created above
await createDomainObjectWithDefaults(page, {
type: 'Sine Wave Generator',
name: 'Telemetry',
parent: displayLayout.uuid //reference the display layout in the creation process
});
});
test('Can directly navigate to a Display Layout with embedded telemetry', async ({ page }) => {
//Now you can directly navigate to the displayLayout created in the beforeEach with the embedded telemetry
await page.goto(displayLayout.url);
//Expect the created Telemetry Object to be visible when directly navigating to the displayLayout
await expect(page.getByTitle('Sine')).toBeVisible();
});
});
/**
* Structure:
* Custom functions should be declared last.

View File

@@ -0,0 +1,260 @@
/*****************************************************************************
* Open MCT, Copyright (c) 2014-2022, United States Government
* as represented by the Administrator of the National Aeronautics and Space
* Administration. All rights reserved.
*
* Open MCT is licensed under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0.
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*
* Open MCT includes source code licensed under additional open source
* licenses. See the Open Source Licenses file (LICENSES.md) included with
* this source code distribution or the Licensing information page available
* at runtime from the About dialog for additional information.
*****************************************************************************/
/* global __dirname */
/**
* This test suite is dedicated to generating LocalStorage via Session Storage to be used
* in some visual test suites like controlledClock.visual.spec.js. This suite should run to completion
* and generate an artifact in ./e2e/test-data/<name>_storage.json . This will run
* on every commit to ensure that this object still loads into tests correctly and will retain the
* *.e2e.spec.js suffix.
*
* TODO: Provide additional validation of object properties as it grows.
* Verification of object properties happens in this file before the test-data is generated,
* and is additionally verified in the validation test suites below.
*/
const { test, expect } = require('../../pluginFixtures.js');
const {
createDomainObjectWithDefaults,
createExampleTelemetryObject,
selectInspectorTab
} = require('../../appActions.js');
const { MISSION_TIME } = require('../../constants.js');
const path = require('path');
const overlayPlotName = 'Overlay Plot with Telemetry Object';
test.describe('Generate Visual Test Data @localStorage @generatedata', () => {
test.use({
clockOptions: {
now: MISSION_TIME,
shouldAdvanceTime: true
}
});
test.beforeEach(async ({ page }) => {
// Go to baseURL
await page.goto('./', { waitUntil: 'domcontentloaded' });
});
// TODO: Visual test for the generated object here
// - Move to using appActions to create the overlay plot
// and embedded standard telemetry object
test('Generate Overlay Plot with Telemetry Object', async ({ page, context }) => {
// Create Overlay Plot
const overlayPlot = await createDomainObjectWithDefaults(page, {
type: 'Overlay Plot',
name: overlayPlotName
});
// Create Telemetry Object
const exampleTelemetry = await createExampleTelemetryObject(page);
// Make Link from Telemetry Object to Overlay Plot
await page.locator('button[title="More options"]').click();
// Select 'Create Link' from dropdown
await page.getByRole('menuitem', { name: ' Create Link' }).click();
// Search and Select for overlay Plot within Create Modal
await page.getByRole('dialog').getByRole('searchbox', { name: 'Search Input' }).click();
await page
.getByRole('dialog')
.getByRole('searchbox', { name: 'Search Input' })
.fill(overlayPlot.name);
await page
.getByRole('treeitem', { name: new RegExp(overlayPlot.name) })
.locator('a')
.click();
await page.getByRole('button', { name: 'Save' }).click();
await page.goto(overlayPlot.url);
// TODO: Flesh Out Assertions against created Objects
await expect(page.locator('.l-browse-bar__object-name')).toContainText(overlayPlotName);
await selectInspectorTab(page, 'Config');
await page
.getByRole('list', { name: 'Plot Series Properties' })
.locator('span')
.first()
.click();
// TODO: Modify the Overlay Plot to use fixed Scaling
// TODO: Verify Autoscaling.
// TODO: Fix accessibility of Plot Series Properties tables
// Assert that the Plot Series properties have the correct values
await expect(
page.locator('[role=cell]:has-text("Value")~[role=cell]:has-text("sin")')
).toBeVisible();
await expect(
page.locator(
'[role=cell]:has-text("Line Method")~[role=cell]:has-text("Linear interpolation")'
)
).toBeVisible();
await expect(
page.locator('[role=cell]:has-text("Markers")~[role=cell]:has-text("Point: 2px")')
).toBeVisible();
await expect(
page.locator('[role=cell]:has-text("Alarm Markers")~[role=cell]:has-text("Enabled")')
).toBeVisible();
await expect(
page.locator('[role=cell]:has-text("Limit Lines")~[role=cell]:has-text("Disabled")')
).toBeVisible();
await page.goto(exampleTelemetry.url);
await selectInspectorTab(page, 'Properties');
// TODO: assert Example Telemetry property values
// await page.goto(exampleTelemetry.url);
// Save localStorage for future test execution
await context.storageState({
path: path.join(__dirname, '../../../e2e/test-data/overlay_plot_storage.json')
});
});
// TODO: Merge this with previous test. Edit object created in previous test.
test('Generate Overlay Plot with 5s Delay', async ({ page, context }) => {
// add overlay plot with defaults
const overlayPlot = await createDomainObjectWithDefaults(page, {
type: 'Overlay Plot',
name: 'Overlay Plot with 5s Delay'
});
const swgWith5sDelay = await createExampleTelemetryObject(page, overlayPlot.uuid);
await page.goto(swgWith5sDelay.url);
await page.getByTitle('More options').click();
await page.getByRole('menuitem', { name: ' Edit Properties...' }).click();
//Edit Example Telemetry Object to include 5s loading Delay
await page.locator('[aria-label="Loading Delay \\(ms\\)"]').fill('5000');
await Promise.all([
page.waitForNavigation(),
page.locator('text=OK').click(),
//Wait for Save Banner to appear
page.waitForSelector('.c-message-banner__message')
]);
// focus the overlay plot
await page.goto(overlayPlot.url);
await expect(page.locator('.l-browse-bar__object-name')).toContainText(overlayPlot.name);
// Clear Recently Viewed
await page.getByRole('button', { name: 'Clear Recently Viewed' }).click();
await page.getByRole('button', { name: 'OK' }).click();
//Save localStorage for future test execution
await context.storageState({
path: path.join(__dirname, '../../../e2e/test-data/overlay_plot_with_delay_storage.json')
});
});
});
test.describe('Validate Overlay Plot with Telemetry Object @localStorage @generatedata', () => {
test.use({
storageState: path.join(__dirname, '../../../e2e/test-data/overlay_plot_storage.json')
});
test('Validate Overlay Plot with Telemetry Object', async ({ page }) => {
await page.goto('./', { waitUntil: 'domcontentloaded' });
await page.locator('a').filter({ hasText: overlayPlotName }).click();
// TODO: Flesh Out Assertions against created Objects
await expect(page.locator('.l-browse-bar__object-name')).toContainText(overlayPlotName);
await selectInspectorTab(page, 'Config');
await page
.getByRole('list', { name: 'Plot Series Properties' })
.locator('span')
.first()
.click();
// TODO: Modify the Overlay Plot to use fixed Scaling
// TODO: Verify Autoscaling.
// TODO: Fix accessibility of Plot Series Properties tables
// Assert that the Plot Series properties have the correct values
await expect(
page.locator('[role=cell]:has-text("Value")~[role=cell]:has-text("sin")')
).toBeVisible();
await expect(
page.locator(
'[role=cell]:has-text("Line Method")~[role=cell]:has-text("Linear interpolation")'
)
).toBeVisible();
await expect(
page.locator('[role=cell]:has-text("Markers")~[role=cell]:has-text("Point: 2px")')
).toBeVisible();
await expect(
page.locator('[role=cell]:has-text("Alarm Markers")~[role=cell]:has-text("Enabled")')
).toBeVisible();
await expect(
page.locator('[role=cell]:has-text("Limit Lines")~[role=cell]:has-text("Disabled")')
).toBeVisible();
});
});
test.describe('Validate Overlay Plot with 5s Delay Telemetry Object @localStorage @generatedata', () => {
test.use({
storageState: path.join(
__dirname,
'../../../e2e/test-data/overlay_plot_with_delay_storage.json'
)
});
test('Validate Overlay Plot with Telemetry Object', async ({ page }) => {
const plotName = 'Overlay Plot with 5s Delay';
await page.goto('./', { waitUntil: 'domcontentloaded' });
await page.locator('a').filter({ hasText: plotName }).click();
// TODO: Flesh Out Assertions against created Objects
await expect(page.locator('.l-browse-bar__object-name')).toContainText(plotName);
await selectInspectorTab(page, 'Config');
await page
.getByRole('list', { name: 'Plot Series Properties' })
.locator('span')
.first()
.click();
// TODO: Modify the Overlay Plot to use fixed Scaling
// TODO: Verify Autoscaling.
// TODO: Fix accessibility of Plot Series Properties tables
// Assert that the Plot Series properties have the correct values
await expect(
page.locator('[role=cell]:has-text("Value")~[role=cell]:has-text("sin")')
).toBeVisible();
await expect(
page.locator(
'[role=cell]:has-text("Line Method")~[role=cell]:has-text("Linear interpolation")'
)
).toBeVisible();
await expect(
page.locator('[role=cell]:has-text("Markers")~[role=cell]:has-text("Point: 2px")')
).toBeVisible();
await expect(
page.locator('[role=cell]:has-text("Alarm Markers")~[role=cell]:has-text("Enabled")')
).toBeVisible();
await expect(
page.locator('[role=cell]:has-text("Limit Lines")~[role=cell]:has-text("Disabled")')
).toBeVisible();
});
});

View File

@@ -1,64 +0,0 @@
/*****************************************************************************
* Open MCT, Copyright (c) 2014-2023, United States Government
* as represented by the Administrator of the National Aeronautics and Space
* Administration. All rights reserved.
*
* Open MCT is licensed under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0.
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*
* Open MCT includes source code licensed under additional open source
* licenses. See the Open Source Licenses file (LICENSES.md) included with
* this source code distribution or the Licensing information page available
* at runtime from the About dialog for additional information.
*****************************************************************************/
/*
This test suite is dedicated to generating LocalStorage via Session Storage to be used
in some visual test suites like controlledClock.visual.spec.js. This suite should run to completion
and generate an artifact named ./e2e/test-data/VisualTestData_storage.json . This will run
on every Commit to ensure that this object still loads into tests correctly and will retain the
.e2e.spec.js suffix.
TODO: Provide additional validation of object properties as it grows.
*/
const { createDomainObjectWithDefaults } = require('../../appActions.js');
const { test, expect } = require('../../pluginFixtures.js');
test('Generate Visual Test Data @localStorage', async ({ page, context }) => {
//Go to baseURL
await page.goto('./', { waitUntil: 'domcontentloaded' });
const overlayPlot = await createDomainObjectWithDefaults(page, { type: 'Overlay Plot' });
// click create button
await page.locator('button:has-text("Create")').click();
// add sine wave generator with defaults
await page.locator('li[role="menuitem"]:has-text("Sine Wave Generator")').click();
//Add a 5000 ms Delay
await page.locator('[aria-label="Loading Delay \\(ms\\)"]').fill('5000');
await Promise.all([
page.waitForNavigation(),
page.locator('button:has-text("OK")').click(),
//Wait for Save Banner to appear
page.waitForSelector('.c-message-banner__message')
]);
// focus the overlay plot
await page.goto(overlayPlot.url);
await expect(page.locator('.l-browse-bar__object-name')).toContainText(overlayPlot.name);
//Save localStorage for future test execution
await context.storageState({ path: './e2e/test-data/VisualTestData_storage.json' });
});

View File

@@ -19,18 +19,18 @@
* this source code distribution or the Licensing information page available
* at runtime from the About dialog for additional information.
*****************************************************************************/
/* global __dirname */
const { test, expect, streamToString } = require('../../../../pluginFixtures');
const { openObjectTreeContextMenu } = require('../../../../appActions');
const {
openObjectTreeContextMenu,
createDomainObjectWithDefaults
} = require('../../../../appActions');
const path = require('path');
const nbUtils = require('../../../../helper/notebookUtils');
lockPage,
dragAndDropEmbed,
enterTextEntry,
startAndAddRestrictedNotebookObject
} = require('../../../../helper/notebookUtils');
const TEST_TEXT = 'Testing text for entries.';
const TEST_TEXT_NAME = 'Test Page';
const CUSTOM_NAME = 'CUSTOM_NAME';
test.describe('Restricted Notebook', () => {
let notebook;
@@ -68,7 +68,7 @@ test.describe('Restricted Notebook', () => {
});
test('Can be locked if at least one page has one entry @addInit', async ({ page }) => {
await nbUtils.enterTextEntry(page, TEST_TEXT);
await enterTextEntry(page, TEST_TEXT);
const commitButton = page.locator('button:has-text("Commit Entries")');
expect(await commitButton.count()).toEqual(1);
@@ -79,7 +79,7 @@ test.describe('Restricted Notebook with at least one entry and with the page loc
let notebook;
test.beforeEach(async ({ page }) => {
notebook = await startAndAddRestrictedNotebookObject(page);
await nbUtils.enterTextEntry(page, TEST_TEXT);
await enterTextEntry(page, TEST_TEXT);
await lockPage(page);
// open sidebar
@@ -125,7 +125,7 @@ test.describe('Restricted Notebook with at least one entry and with the page loc
expect.soft(newPageCount).toEqual(1);
// enter test text
await nbUtils.enterTextEntry(page, TEST_TEXT);
await enterTextEntry(page, TEST_TEXT);
// expect new page to be lockable
const commitButton = page.getByRole('button', { name: ' Commit Entries' });
@@ -136,7 +136,7 @@ test.describe('Restricted Notebook with at least one entry and with the page loc
// Delete the page
await page.getByRole('menuitem', { name: 'Delete Page' }).click();
// Click OK button
await page.getByRole('button', { name: 'Ok' }).click();
await page.getByRole('button', { name: 'Ok', exact: true }).click();
// deleted page, should no longer exist
const deletedPageElement = page.getByText(TEST_TEXT_NAME);
@@ -147,12 +147,12 @@ test.describe('Restricted Notebook with at least one entry and with the page loc
test.describe('Restricted Notebook with a page locked and with an embed @addInit', () => {
test.beforeEach(async ({ page }) => {
const notebook = await startAndAddRestrictedNotebookObject(page);
await nbUtils.dragAndDropEmbed(page, notebook);
await dragAndDropEmbed(page, notebook);
});
test('Allows embeds to be deleted if page unlocked @addInit', async ({ page }) => {
// Click .c-ne__embed__name .c-popup-menu-button
await page.locator('.c-ne__embed__name .c-icon-button').click(); // embed popup menu
// Click embed popup menu
await page.locator('.c-ne__embed__name .c-icon-button').click();
const embedMenu = page.locator('body >> .c-menu');
await expect(embedMenu).toContainText('Remove This Embed');
@@ -160,8 +160,8 @@ test.describe('Restricted Notebook with a page locked and with an embed @addInit
test('Disallows embeds to be deleted if page locked @addInit', async ({ page }) => {
await lockPage(page);
// Click .c-ne__embed__name .c-popup-menu-button
await page.locator('.c-ne__embed__name .c-icon-button').click(); // embed popup menu
// Click embed popup menu
await page.locator('.c-ne__embed__name .c-icon-button').click();
const embedMenu = page.locator('body >> .c-menu');
await expect(embedMenu).not.toContainText('Remove This Embed');
@@ -174,7 +174,7 @@ test.describe('can export restricted notebook as text', () => {
});
test('basic functionality ', async ({ page }) => {
await nbUtils.enterTextEntry(page, `Foo bar entry`);
await enterTextEntry(page, `Foo bar entry`);
// Click on 3 Dot Menu
await page.locator('button[title="More options"]').click();
const downloadPromise = page.waitForEvent('download');
@@ -182,6 +182,8 @@ test.describe('can export restricted notebook as text', () => {
await page.getByRole('menuitem', { name: /Export Notebook as Text/ }).click();
await page.getByRole('button', { name: 'Save' }).click();
//Verify exported text as a stream of text instead of a file read from the filesystem
const download = await downloadPromise;
const readStream = await download.createReadStream();
const exportedText = await streamToString(readStream);
@@ -193,26 +195,3 @@ test.describe('can export restricted notebook as text', () => {
test.fixme('can export all notebook tags', async ({ page }) => {});
test.fixme('can export all notebook snapshots', async ({ page }) => {});
});
/**
* @param {import('@playwright/test').Page} page
*/
async function startAndAddRestrictedNotebookObject(page) {
await page.addInitScript({
path: path.join(__dirname, '../../../../helper/', 'addInitRestrictedNotebook.js')
});
await page.goto('./', { waitUntil: 'domcontentloaded' });
return createDomainObjectWithDefaults(page, { type: CUSTOM_NAME });
}
/**
* @param {import('@playwright/test').Page} page
*/
async function lockPage(page) {
const commitButton = page.locator('button:has-text("Commit Entries")');
await commitButton.click();
//Wait until Lock Banner is visible
await page.locator('text=Lock Page').click();
}

View File

@@ -26,56 +26,11 @@ This test suite is dedicated to tests which verify notebook tag functionality.
const { test, expect } = require('../../../../pluginFixtures');
const { createDomainObjectWithDefaults, selectInspectorTab } = require('../../../../appActions');
const nbUtils = require('../../../../helper/notebookUtils');
/**
* Creates a notebook object and adds an entry.
* @param {import('@playwright/test').Page} - page to load
* @param {number} [iterations = 1] - the number of entries to create
*/
async function createNotebookAndEntry(page, iterations = 1) {
const notebook = createDomainObjectWithDefaults(page, { type: 'Notebook' });
for (let iteration = 0; iteration < iterations; iteration++) {
await nbUtils.enterTextEntry(page, `Entry ${iteration}`);
}
return notebook;
}
/**
* Creates a notebook object, adds an entry, and adds a tag.
* @param {import('@playwright/test').Page} page
* @param {number} [iterations = 1] - the number of entries (and tags) to create
*/
async function createNotebookEntryAndTags(page, iterations = 1) {
const notebook = await createNotebookAndEntry(page, iterations);
await selectInspectorTab(page, 'Annotations');
for (let iteration = 0; iteration < iterations; iteration++) {
// Hover and click "Add Tag" button
// Hover is needed here to "slow down" the actions while running in headless mode
await page.locator(`[aria-label="Notebook Entry"] >> nth = ${iteration}`).click();
await page.hover(`button:has-text("Add Tag")`);
await page.locator(`button:has-text("Add Tag")`).click();
// Click inside the tag search input
await page.locator('[placeholder="Type to select tag"]').click();
// Select the "Driving" tag
await page.locator('[aria-label="Autocomplete Options"] >> text=Driving').click();
// Hover and click "Add Tag" button
// Hover is needed here to "slow down" the actions while running in headless mode
await page.hover(`button:has-text("Add Tag")`);
await page.locator(`button:has-text("Add Tag")`).click();
// Click inside the tag search input
await page.locator('[placeholder="Type to select tag"]').click();
// Select the "Science" tag
await page.locator('[aria-label="Autocomplete Options"] >> text=Science').click();
}
return notebook;
}
const {
enterTextEntry,
createNotebookAndEntry,
createNotebookEntryAndTags
} = require('../../../../helper/notebookUtils');
test.describe('Tagging in Notebooks @addInit', () => {
test.beforeEach(async ({ page }) => {
@@ -112,7 +67,7 @@ test.describe('Tagging in Notebooks @addInit', () => {
await createDomainObjectWithDefaults(page, { type: 'Notebook' });
await selectInspectorTab(page, 'Annotations');
await nbUtils.enterTextEntry(page, '');
await enterTextEntry(page, '');
await page.hover(`button:has-text("Add Tag")`);
await page.locator(`button:has-text("Add Tag")`).click();

View File

@@ -1,63 +0,0 @@
/* eslint-disable no-undef */
/*****************************************************************************
* Open MCT, Copyright (c) 2014-2023, United States Government
* as represented by the Administrator of the National Aeronautics and Space
* Administration. All rights reserved.
*
* Open MCT is licensed under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0.
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*
* Open MCT includes source code licensed under additional open source
* licenses. See the Open Source Licenses file (LICENSES.md) included with
* this source code distribution or the Licensing information page available
* at runtime from the About dialog for additional information.
*****************************************************************************/
/* global __dirname */
/*
Collection of Visual Tests set to run with modified init scripts to inject plugins not otherwise available in the default contexts.
These should only use functional expect statements to verify assumptions about the state
in a test and not for functional verification of correctness. Visual tests are not supposed
to "fail" on assertions. Instead, they should be used to detect changes between builds or branches.
Note: Larger testsuite sizes are OK due to the setup time associated with these tests.
*/
// eslint-disable-next-line no-unused-vars
const { test, expect } = require('../../pluginFixtures');
const { createDomainObjectWithDefaults } = require('../../appActions');
const percySnapshot = require('@percy/playwright');
const path = require('path');
const CUSTOM_NAME = 'CUSTOM_NAME';
test.describe('Visual - addInit', () => {
test.use({
clockOptions: {
now: 0, //Set browser clock to UNIX Epoch
shouldAdvanceTime: false //Don't advance the clock
}
});
test('Restricted Notebook is visually correct @addInit @unstable', async ({ page, theme }) => {
await page.addInitScript({
path: path.join(__dirname, '../../helper', './addInitRestrictedNotebook.js')
});
//Go to baseURL
await page.goto('./#/browse/mine', { waitUntil: 'networkidle' });
await page.getByTitle('Collapse Browse Pane').click();
await createDomainObjectWithDefaults(page, { type: CUSTOM_NAME });
// Take a snapshot of the newly created CUSTOM_NAME notebook
await percySnapshot(page, `Restricted Notebook with CUSTOM_NAME (theme: '${theme}')`);
});
});

View File

@@ -21,15 +21,32 @@
*****************************************************************************/
/*
* This test suite template is to be used when verifying Test Data files found in /e2e/test-data/
*/
Tests the branding associated with the default deployment. At least the about modal for now
*/
const { test } = require('../../baseFixtures');
const { test, expect } = require('../../../pluginFixtures');
const percySnapshot = require('@percy/playwright');
test.describe('recycled_local_storage @localStorage', () => {
//We may want to do some additional level of verification of this file. For now, we just verify that it exists and can be used in a test suite.
test.use({ storageState: './e2e/test-data/recycled_local_storage.json' });
test('Can use recycled_local_storage file', async ({ page }) => {
await page.goto('./', { waitUntil: 'domcontentloaded' });
test.describe('Visual - Branding', () => {
test.beforeEach(async ({ page }) => {
//Go to baseURL and Hide Tree
await page.goto('./#/browse/mine?hideTree=true', { waitUntil: 'networkidle' });
});
test('Visual - About Modal', async ({ page, theme }) => {
// Click About button
await page.click('.l-shell__app-logo');
// Modify the Build information in 'about' to be consistent run-over-run
const versionInformationLocator = page.locator('ul.t-info.l-info.s-info').first();
await expect(versionInformationLocator).toBeEnabled();
await versionInformationLocator.evaluate(
(node) =>
(node.innerHTML =
'<li>Version: visual-snapshot</li> <li>Build Date: Mon Nov 15 2021 08:07:51 GMT-0800 (Pacific Standard Time)</li> <li>Revision: 93049cdbc6c047697ca204893db9603b864b8c9f</li> <li>Branch: master</li>')
);
// Take a snapshot of the About modal
await percySnapshot(page, `About (theme: '${theme}')`);
});
});

View File

@@ -21,7 +21,10 @@
*****************************************************************************/
const { test } = require('../../../pluginFixtures.js');
const { createDomainObjectWithDefaults } = require('../../../appActions.js');
const {
expandTreePaneItemByName,
createDomainObjectWithDefaults
} = require('../../../appActions.js');
const percySnapshot = require('@percy/playwright');
@@ -88,14 +91,3 @@ test.describe('Visual - Tree Pane', () => {
});
});
});
/**
* @param {import('@playwright/test').Page} page
* @param {string} name
*/
async function expandTreePaneItemByName(page, name) {
const treePane = page.getByTestId('tree-pane');
const treeItem = treePane.locator(`role=treeitem[expanded=false][name=/${name}/]`);
const expandTriangle = treeItem.locator('.c-disclosure-triangle');
await expandTriangle.click();
}

View File

@@ -21,20 +21,21 @@
*****************************************************************************/
/*
Collection of Visual Tests set to run in a default context. The tests within this suite
are only meant to run against openmct started by `npm start` within the
`./e2e/playwright-visual.config.js` file.
Collection of Visual Tests set to run with browser clock manipulate made possible with the
clockOptions plugin fixture.
*/
const { test, expect } = require('../../pluginFixtures');
const percySnapshot = require('@percy/playwright');
test.describe('Visual - Controlled Clock @localStorage', () => {
test.describe('Visual - Controlled Clock', () => {
test.beforeEach(async ({ page }) => {
//Go to baseURL and Hide Tree
await page.goto('./#/browse/mine?hideTree=true', { waitUntil: 'networkidle' });
});
test.use({
storageState: './e2e/test-data/VisualTestData_storage.json',
storageState: './e2e/test-data/overlay_plot_with_delay_storage.json',
clockOptions: {
now: 0, //Set browser clock to UNIX Epoch
shouldAdvanceTime: false //Don't advance the clock
}
});
@@ -43,10 +44,14 @@ test.describe('Visual - Controlled Clock @localStorage', () => {
// Go to baseURL
await page.goto('./#/browse/mine', { waitUntil: 'networkidle' });
await page.getByTitle('Collapse Browse Pane').click();
await page.locator('a:has-text("Unnamed Overlay Plot Overlay Plot")').click();
await page
.locator('a')
.filter({ hasText: 'Overlay Plot with Telemetry Object Overlay Plot' })
.click();
//Ensure that we're on the Unnamed Overlay Plot object
await expect(page.locator('.l-browse-bar__object-name')).toContainText('Unnamed Overlay Plot');
await expect(page.locator('.l-browse-bar__object-name')).toContainText(
'Overlay Plot with Telemetry Object'
);
//Wait for canvas to be rendered and stop animating
await page.locator('canvas >> nth=1').hover({ trial: true });

View File

@@ -1,173 +0,0 @@
/*****************************************************************************
* Open MCT, Copyright (c) 2014-2023, United States Government
* as represented by the Administrator of the National Aeronautics and Space
* Administration. All rights reserved.
*
* Open MCT is licensed under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0.
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*
* Open MCT includes source code licensed under additional open source
* licenses. See the Open Source Licenses file (LICENSES.md) included with
* this source code distribution or the Licensing information page available
* at runtime from the About dialog for additional information.
*****************************************************************************/
/*
Collection of Visual Tests set to run in a default context. The tests within this suite
are only meant to run against openmct started by `npm start` within the
`./e2e/playwright-visual.config.js` file.
These should only use functional expect statements to verify assumptions about the state
in a test and not for functional verification of correctness. Visual tests are not supposed
to "fail" on assertions. Instead, they should be used to detect changes between builds or branches.
Note: Larger testsuite sizes are OK due to the setup time associated with these tests.
*/
const { test, expect } = require('../../pluginFixtures');
const percySnapshot = require('@percy/playwright');
const { createDomainObjectWithDefaults } = require('../../appActions');
test.describe('Visual - Default', () => {
test.beforeEach(async ({ page }) => {
//Go to baseURL and Hide Tree
await page.goto('./#/browse/mine', { waitUntil: 'networkidle' });
await page.getByTitle('Collapse Browse Pane').click();
});
test.use({
clockOptions: {
now: 0, //Set browser clock to UNIX Epoch
shouldAdvanceTime: false //Don't advance the clock
}
});
test('Visual - Root and About', async ({ page, theme }) => {
// Verify that Create button is actionable
await expect(page.locator('button:has-text("Create")')).toBeEnabled();
// Take a snapshot of the Dashboard
await percySnapshot(page, `Root (theme: '${theme}')`);
// Click About button
await page.click('.l-shell__app-logo');
// Modify the Build information in 'about' to be consistent run-over-run
const versionInformationLocator = page.locator('ul.t-info.l-info.s-info').first();
await expect(versionInformationLocator).toBeEnabled();
await versionInformationLocator.evaluate(
(node) =>
(node.innerHTML =
'<li>Version: visual-snapshot</li> <li>Build Date: Mon Nov 15 2021 08:07:51 GMT-0800 (Pacific Standard Time)</li> <li>Revision: 93049cdbc6c047697ca204893db9603b864b8c9f</li> <li>Branch: master</li>')
);
// Take a snapshot of the About modal
await percySnapshot(page, `About (theme: '${theme}')`);
});
test('Visual - Default Condition Set @unstable', async ({ page, theme }) => {
await createDomainObjectWithDefaults(page, { type: 'Condition Set' });
// Take a snapshot of the newly created Condition Set object
await percySnapshot(page, `Default Condition Set (theme: '${theme}')`);
});
test('Visual - Default Condition Widget @unstable', async ({ page, theme }) => {
test.info().annotations.push({
type: 'issue',
description: 'https://github.com/nasa/openmct/issues/5349'
});
await createDomainObjectWithDefaults(page, { type: 'Condition Widget' });
// Take a snapshot of the newly created Condition Widget object
await percySnapshot(page, `Default Condition Widget (theme: '${theme}')`);
});
test('Visual - Time Conductor start time is less than end time', async ({ page, theme }) => {
const year = new Date().getFullYear();
let startDate = 'xxxx-01-01 01:00:00.000Z';
startDate = year + startDate.substring(4);
let endDate = 'xxxx-01-01 02:00:00.000Z';
endDate = year + endDate.substring(4);
await page.getByRole('button', { name: 'Time Conductor Settings' }).click();
await page.locator('input[type="text"]').nth(1).fill(endDate.toString());
await page.locator('input[type="text"]').first().fill(startDate.toString());
// verify no error msg
await percySnapshot(page, `Default Time conductor (theme: '${theme}')`);
startDate = year + 1 + startDate.substring(4);
await page.locator('input[type="text"]').first().fill(startDate.toString());
await page.locator('input[type="text"]').nth(1).click();
// verify error msg for start time (unable to capture snapshot of popup)
await percySnapshot(page, `Start time error (theme: '${theme}')`);
startDate = year - 1 + startDate.substring(4);
await page.locator('input[type="text"]').first().fill(startDate.toString());
endDate = year - 2 + endDate.substring(4);
await page.locator('input[type="text"]').nth(1).fill(endDate.toString());
await page.locator('input[type="text"]').first().click();
// verify error msg for end time (unable to capture snapshot of popup)
await percySnapshot(page, `End time error (theme: '${theme}')`);
});
test('Visual - Sine Wave Generator Form', async ({ page, theme }) => {
//Click the Create button
await page.click('button:has-text("Create")');
// Click text=Sine Wave Generator
await page.click('text=Sine Wave Generator');
await percySnapshot(page, `Default Sine Wave Generator Form (theme: '${theme}')`);
await page.locator('.field.control.l-input-sm input').first().click();
await page.locator('.field.control.l-input-sm input').first().fill('');
// Validate red x mark
await percySnapshot(page, `removed amplitude property value (theme: '${theme}')`);
});
test('Visual - Save Successful Banner @unstable', async ({ page, theme }) => {
await createDomainObjectWithDefaults(page, { type: 'Timer' });
await page.locator('.c-message-banner__message').hover({ trial: true });
await percySnapshot(page, `Banner message shown (theme: '${theme}')`);
//Wait until Save Banner is gone
await page.locator('.c-message-banner__close-button').click();
await page.waitForSelector('.c-message-banner__message', { state: 'detached' });
await percySnapshot(page, `Banner message gone (theme: '${theme}')`);
});
test('Visual - Display Layout Icon is correct', async ({ page, theme }) => {
//Click the Create button
await page.click('button:has-text("Create")');
//Hover on Display Layout option.
await page.locator('text=Display Layout').hover();
await percySnapshot(page, `Display Layout Create Menu (theme: '${theme}')`);
});
test('Visual - Default Gauge is correct @unstable', async ({ page, theme }) => {
await createDomainObjectWithDefaults(page, { type: 'Gauge' });
// Take a snapshot of the newly created Gauge object
await percySnapshot(page, `Default Gauge (theme: '${theme}')`);
});
});

View File

@@ -0,0 +1,101 @@
/*****************************************************************************
* Open MCT, Copyright (c) 2014-2023, United States Government
* as represented by the Administrator of the National Aeronautics and Space
* Administration. All rights reserved.
*
* Open MCT is licensed under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0.
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*
* Open MCT includes source code licensed under additional open source
* licenses. See the Open Source Licenses file (LICENSES.md) included with
* this source code distribution or the Licensing information page available
* at runtime from the About dialog for additional information.
*****************************************************************************/
/*
Collection of Visual Tests set to run in a default context with default Plugins. The tests within this suite
are only meant to run against openmct's app.js started by `npm run start` within the
`./e2e/playwright-visual.config.js` file.
*/
const { test, expect } = require('../../pluginFixtures');
const percySnapshot = require('@percy/playwright');
const { createDomainObjectWithDefaults } = require('../../appActions');
test.describe('Visual - Default', () => {
test.beforeEach(async ({ page }) => {
//Go to baseURL and Hide Tree
await page.goto('./#/browse/mine?hideTree=true', { waitUntil: 'networkidle' });
});
test('Visual - Default Dashboard', async ({ page, theme }) => {
// Verify that Create button is actionable
await expect(page.locator('button:has-text("Create")')).toBeEnabled();
// Take a snapshot of the Dashboard
await percySnapshot(page, `Default Dashboard (theme: '${theme}')`);
});
test('Visual - Default Condition Set', async ({ page, theme }) => {
await createDomainObjectWithDefaults(page, {
type: 'Condition Set',
name: 'Default Condition Set'
});
// Take a snapshot of the newly created Condition Set object
await percySnapshot(page, `Default Condition Set (theme: '${theme}')`);
});
test('Visual - Default Condition Widget', async ({ page, theme }) => {
await createDomainObjectWithDefaults(page, {
type: 'Condition Widget',
name: 'Default Condition Widget'
});
// Take a snapshot of the newly created Condition Widget object
await percySnapshot(page, `Default Condition Widget (theme: '${theme}')`);
});
test('Visual - Sine Wave Generator Form', async ({ page, theme }) => {
//Click the Create button
await page.click('button:has-text("Create")');
// Click text=Sine Wave Generator
await page.click('text=Sine Wave Generator');
await percySnapshot(page, `Default Sine Wave Generator Form (theme: '${theme}')`);
await page.locator('.field.control.l-input-sm input').first().click();
await page.locator('.field.control.l-input-sm input').first().fill('');
// Validate red x mark
await percySnapshot(page, `removed amplitude property value (theme: '${theme}')`);
});
test('Visual - Display Layout Icon is correct in Create Menu', async ({ page, theme }) => {
// Click the Create button
await page.click('button:has-text("Create")');
// Hover on Display Layout option.
await page.locator('text=Display Layout').hover();
await percySnapshot(page, `Display Layout Create Menu (theme: '${theme}')`);
});
test('Visual - Default Gauge', async ({ page, theme }) => {
await createDomainObjectWithDefaults(page, {
type: 'Gauge',
name: 'Default Gauge'
});
// Take a snapshot of the newly created Gauge object
await percySnapshot(page, `Default Gauge (theme: '${theme}')`);
});
});

View File

@@ -26,12 +26,12 @@ const percySnapshot = require('@percy/playwright');
const utils = require('../../helper/faultUtils');
test.describe('The Fault Management Plugin Visual Test', () => {
test.describe('Fault Management Visual Tests', () => {
test('icon test', async ({ page, theme }) => {
await page.addInitScript({
path: path.join(__dirname, '../../helper/', 'addInitFaultManagementPlugin.js')
});
await page.goto('./', { waitUntil: 'networkidle' });
await page.goto('./', { waitUntil: 'domcontentloaded' });
await percySnapshot(page, `Fault Management icon appears in tree (theme: '${theme}')`);
});

View File

@@ -22,14 +22,35 @@
const { test } = require('../../pluginFixtures');
const percySnapshot = require('@percy/playwright');
const { expandTreePaneItemByName, createDomainObjectWithDefaults } = require('../../appActions');
const {
selectInspectorTab,
expandTreePaneItemByName,
createDomainObjectWithDefaults
} = require('../../appActions');
const {
startAndAddRestrictedNotebookObject,
enterTextEntry
} = require('../../helper/notebookUtils');
test.describe('Visual - Restricted Notebook', () => {
test.beforeEach(async ({ page }) => {
await startAndAddRestrictedNotebookObject(page);
});
test('Restricted Notebook is visually correct @addInit', async ({ page, theme }) => {
// Take a snapshot of the newly created CUSTOM_NAME notebook
await percySnapshot(page, `Restricted Notebook with CUSTOM_NAME (theme: '${theme}')`);
});
});
test.describe('Visual - Notebook', () => {
test.beforeEach(async ({ page }) => {
//Go to baseURL and Hide Tree
await page.goto('./#/browse/mine?hideTree=true', { waitUntil: 'networkidle' });
});
test('Accepts dropped objects as embeds @unstable', async ({ page, theme, openmctConfig }) => {
const { myItemsFolderName } = openmctConfig;
await page.goto('./#/browse/mine', { waitUntil: 'networkidle' });
// Create Notebook
const notebook = await createDomainObjectWithDefaults(page, {
type: 'Notebook',
name: 'Embed Test Notebook'
@@ -43,8 +64,36 @@ test.describe('Visual - Notebook', () => {
await expandTreePaneItemByName(page, myItemsFolderName);
await page.goto(notebook.url);
await page.dragAndDrop('role=treeitem[name=/Dropped Overlay Plot/]', '.c-notebook__drag-area');
await percySnapshot(page, `Notebook w/ dropped embed (theme: ${theme})`);
});
test("Blur 'Add tag' on Notebook", async ({ page, theme }) => {
await createDomainObjectWithDefaults(page, {
type: 'Notebook',
name: 'Add Tag Test Notebook'
});
await enterTextEntry(page, 'Entry 0');
// Click on Annotations tab
await selectInspectorTab(page, 'Annotations');
// Take snapshot of the notebook with the Annotations tab opened
await percySnapshot(page, `Notebook Annotation (theme: '${theme}')`);
await page.locator('button:has-text("Add Tag")').click();
// Take snapshot of the notebook with the AutoComplete field visible
await percySnapshot(page, `Notebook Add Tag (theme: '${theme}')`);
// Click inside the AutoComplete field
await page.locator('[placeholder="Type to select tag"]').click();
// Click on the "Tags" header (simulating a click outside the autocomplete field)
await page.locator('div.c-inspect-properties__header:has-text("Tags")').click();
// Take snapshot of the notebook with the AutoComplete field hidden and with the "Add Tag" button visible
await percySnapshot(page, `Notebook Annotation de-select blur (theme: '${theme}')`);
});
});

View File

@@ -39,7 +39,10 @@ test.describe("Visual - Check Notification Info Banner of 'Save successful'", ()
theme
}) => {
// Create a clock domain object
await createDomainObjectWithDefaults(page, { type: 'Clock' });
await createDomainObjectWithDefaults(page, {
type: 'Clock',
name: 'Default Clock'
});
// Verify there is a button with aria-label="Review 1 Notification"
expect(await page.locator('button[aria-label="Review 1 Notification"]').isVisible()).toBe(true);
// Verify there is a button with aria-label="Clear all notifications"
@@ -52,12 +55,14 @@ test.describe("Visual - Check Notification Info Banner of 'Save successful'", ()
expect(await page.locator('div[role="dialog"]').isVisible()).toBe(true);
// Verify the div with role="dialog" contains text "Save successful"
expect(await page.locator('div[role="dialog"]').innerText()).toContain('Save successful');
await percySnapshot(page, `Notification banner - ${theme}`);
await percySnapshot(page, `Notification banner shows Save successful (theme: '${theme}')`);
// Verify there is a button with text "Dismiss"
expect(await page.locator('button:has-text("Dismiss")').isVisible()).toBe(true);
await percySnapshot(page, `Notification banner shows Dismiss (theme: '${theme}')`);
// Click on button with text "Dismiss"
await page.locator('button:has-text("Dismiss")').click();
// Verify there is no div with role="dialog"
expect(await page.locator('div[role="dialog"]').isVisible()).toBe(false);
await percySnapshot(page, `Notification banner dismissed (theme: '${theme}')`);
});
});

View File

@@ -30,60 +30,72 @@ const { createDomainObjectWithDefaults } = require('../../appActions');
const percySnapshot = require('@percy/playwright');
test.describe('Grand Search', () => {
let clock;
let displayLayout;
test.beforeEach(async ({ page, theme }) => {
//Go to baseURL and Hide Tree
await page.goto('./#/browse/mine', { waitUntil: 'networkidle' });
await page.goto('./', { waitUntil: 'domcontentloaded' });
await page.getByTitle('Collapse Browse Pane').click();
displayLayout = await createDomainObjectWithDefaults(page, {
type: 'Display Layout',
name: 'Visual Test Display Layout'
});
clock = await createDomainObjectWithDefaults(page, {
type: 'Clock',
name: 'Visual Test Clock',
parent: displayLayout.uuid
});
});
test.use({
clockOptions: {
now: 0, //Set browser clock to UNIX Epoch
shouldAdvanceTime: false //Don't advance the clock
}
});
//This needs to be rewritten to use a non clock or non display layout object
test('Can search for objects, and subsequent search dropdown behaves properly @unstable', async ({
test('Can search for folder object, and subsequent search dropdown behaves properly', async ({
page,
theme
}) => {
// await createDomainObjectWithDefaults(page, 'Display Layout');
// await page.locator('text=Snapshot Save and Finish Editing Save and Continue Editing >> button').nth(1).click();
// await page.locator('text=Save and Finish Editing').click();
const folder1 = 'Folder1';
await createDomainObjectWithDefaults(page, {
type: 'Folder',
name: folder1
});
const searchInput = page.getByRole('searchbox', { name: 'Search Input' });
const searchResults = page.getByRole('searchbox', { name: 'OpenMCT Search' });
// Navigate to display layout
await page.goto(displayLayout.url);
// Click [aria-label="OpenMCT Search"] input[type="search"]
await page.locator('[aria-label="OpenMCT Search"] input[type="search"]').click();
// Fill [aria-label="OpenMCT Search"] input[type="search"]
await page.locator('[aria-label="OpenMCT Search"] input[type="search"]').fill(folder1);
await expect(page.locator('[aria-label="Search Result"]')).toContainText(folder1);
await percySnapshot(page, 'Searching for Folder Object');
// Search for the clock object
await searchInput.click();
await searchInput.fill(clock.name);
await expect(searchResults.getByText('Visual Test Clock')).toBeVisible();
//Searching for an object returns that object in the grandsearch
await percySnapshot(page, `Searching for Clock Object (theme: '${theme}')`);
// Enter Edit mode on the Display Layout
await page.getByRole('button', { name: 'Edit' }).click();
// Navigate to the clock object while in edit mode on the display layout
await searchInput.click();
await searchResults.getByText('Visual Test Clock').click();
await page.locator('[aria-label="OpenMCT Search"] [aria-label="Search Input"]').click();
await page.locator('[aria-label="Unnamed Clock clock result"] >> text=Unnamed Clock').click();
await percySnapshot(
page,
'Preview for clock should display when editing enabled and search item clicked'
`Preview for clock should display when editing enabled and search item clicked (theme: '${theme}')`
);
await page.locator('[aria-label="Close"]').click();
await percySnapshot(page, 'Search should still be showing after preview closed');
// Close the preview
await page.getByRole('button', { name: 'Close' }).click();
await percySnapshot(
page,
`Search should still be showing after preview closed (theme: '${theme}')`
);
await page
.locator('text=Snapshot Save and Finish Editing Save and Continue Editing >> button')
.nth(1)
.click();
// Save and finish editing the Display Layout
await page.getByRole('button', { name: 'Save' }).click();
await page.getByRole('listitem', { name: 'Save and Finish Editing' }).click();
await page.locator('text=Save and Finish Editing').click();
// Search for the clock object
await searchInput.click();
await searchInput.fill(clock.name);
await expect(searchResults.getByText('Visual Test Clock')).toBeVisible();
await page.locator('[aria-label="OpenMCT Search"] [aria-label="Search Input"]').click();
// Navigate to the clock object while not in edit mode on the display layout
await searchResults.getByText('Visual Test Clock').click();
await page.locator('[aria-label="OpenMCT Search"] [aria-label="Search Input"]').fill('Cl');
await Promise.all([page.waitForNavigation(), page.locator('text=Unnamed Clock').click()]);
await percySnapshot(
page,
`Clicking on search results should navigate to them if not editing (theme: '${theme}')`