/***************************************************************************** * 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 tests which verify the basic operations surrounding conditionSets. Note: this suite is sharing state between tests which is considered an anti-pattern. Implimenting in this way to demonstrate some playwright for test developers. This pattern should not be re-used in other CRUD suites. */ const { test, expect } = require('../../../../pluginFixtures.js'); const { createDomainObjectWithDefaults } = require('../../../../appActions'); let conditionSetUrl; let getConditionSetIdentifierFromUrl; test.describe.serial('Condition Set CRUD Operations on @localStorage', () => { test.beforeAll(async ({ browser }) => { //TODO: This needs to be refactored const context = await browser.newContext(); const page = await context.newPage(); await page.goto('./', { waitUntil: 'domcontentloaded' }); await page.click('button:has-text("Create")'); await page.locator('li[role="menuitem"]:has-text("Condition Set")').click(); await Promise.all([page.waitForNavigation(), page.click('button:has-text("OK")')]); //Save localStorage for future test execution await context.storageState({ path: './e2e/test-data/recycled_local_storage.json' }); //Set object identifier from url conditionSetUrl = page.url(); getConditionSetIdentifierFromUrl = conditionSetUrl.split('/').pop().split('?')[0]; console.debug(`getConditionSetIdentifierFromUrl: ${getConditionSetIdentifierFromUrl}`); await page.close(); }); //Load localStorage for subsequent tests test.use({ storageState: './e2e/test-data/recycled_local_storage.json' }); //Begin suite of tests again localStorage test('Condition set object properties persist in main view and inspector @localStorage', async ({ page }) => { //Navigate to baseURL with injected localStorage await page.goto(conditionSetUrl, { waitUntil: 'networkidle' }); //Assertions on loaded Condition Set in main view. This is a stateful transition step after page.goto() await expect .soft(page.locator('.l-browse-bar__object-name')) .toContainText('Unnamed Condition Set'); //Assertions on loaded Condition Set in Inspector expect.soft(page.locator('_vue=item.name=Unnamed Condition Set')).toBeTruthy(); //Reload Page await Promise.all([page.reload(), page.waitForLoadState('networkidle')]); //Re-verify after reload await expect .soft(page.locator('.l-browse-bar__object-name')) .toContainText('Unnamed Condition Set'); //Assertions on loaded Condition Set in Inspector expect.soft(page.locator('_vue=item.name=Unnamed Condition Set')).toBeTruthy(); }); test('condition set object can be modified on @localStorage', async ({ page, openmctConfig }) => { const { myItemsFolderName } = openmctConfig; await page.goto(conditionSetUrl, { waitUntil: 'networkidle' }); //Assertions on loaded Condition Set in main view. This is a stateful transition step after page.goto() await expect .soft(page.locator('.l-browse-bar__object-name')) .toContainText('Unnamed Condition Set'); //Update the Condition Set properties // Click Edit Button await page.locator('text=Conditions View Snapshot >> button').nth(3).click(); //Edit Condition Set Name from main view await page .locator('.l-browse-bar__object-name') .filter({ hasText: 'Unnamed Condition Set' }) .first() .fill('Renamed Condition Set'); await page .locator('.l-browse-bar__object-name') .filter({ hasText: 'Renamed Condition Set' }) .first() .press('Enter'); // Click Save Button await page .locator('text=Snapshot Save and Finish Editing Save and Continue Editing >> button') .nth(1) .click(); // Click Save and Finish Editing Option await page.locator('text=Save and Finish Editing').click(); //Verify Main section reflects updated Name Property await expect .soft(page.locator('.l-browse-bar__object-name')) .toContainText('Renamed Condition Set'); // Verify Inspector properties // Verify Inspector has updated Name property expect.soft(page.locator('text=Renamed Condition Set').nth(1)).toBeTruthy(); // Verify Inspector Details has updated Name property expect.soft(page.locator('text=Renamed Condition Set').nth(2)).toBeTruthy(); // Verify Tree reflects updated Name proprety // Expand Tree await page.locator(`text=Open MCT ${myItemsFolderName} >> span >> nth=3`).click(); // Verify Condition Set Object is renamed in Tree expect(page.locator('a:has-text("Renamed Condition Set")')).toBeTruthy(); // Verify Search Tree reflects renamed Name property await page.locator('[aria-label="OpenMCT Search"] input[type="search"]').fill('Renamed'); expect(page.locator('a:has-text("Renamed Condition Set")')).toBeTruthy(); //Reload Page await Promise.all([page.reload(), page.waitForLoadState('networkidle')]); //Verify Main section reflects updated Name Property await expect .soft(page.locator('.l-browse-bar__object-name')) .toContainText('Renamed Condition Set'); // Verify Inspector properties // Verify Inspector has updated Name property expect.soft(page.locator('text=Renamed Condition Set').nth(1)).toBeTruthy(); // Verify Inspector Details has updated Name property expect.soft(page.locator('text=Renamed Condition Set').nth(2)).toBeTruthy(); // Verify Tree reflects updated Name proprety // Expand Tree await page.locator(`text=Open MCT ${myItemsFolderName} >> span >> nth=3`).click(); // Verify Condition Set Object is renamed in Tree expect(page.locator('a:has-text("Renamed Condition Set")')).toBeTruthy(); // Verify Search Tree reflects renamed Name property await page.locator('[aria-label="OpenMCT Search"] input[type="search"]').fill('Renamed'); expect(page.locator('a:has-text("Renamed Condition Set")')).toBeTruthy(); }); test('condition set object can be deleted by Search Tree Actions menu on @localStorage', async ({ page }) => { //Navigate to baseURL await page.goto('./', { waitUntil: 'domcontentloaded' }); //Assertions on loaded Condition Set in main view. This is a stateful transition step after page.goto() await expect( page.locator('a:has-text("Unnamed Condition Set Condition Set") >> nth=0') ).toBeVisible(); const numberOfConditionSetsToStart = await page .locator('a:has-text("Unnamed Condition Set Condition Set")') .count(); // Search for Unnamed Condition Set await page .locator('[aria-label="OpenMCT Search"] input[type="search"]') .fill('Unnamed Condition Set'); // Click Search Result await page .locator('[aria-label="OpenMCT Search"] >> text=Unnamed Condition Set') .first() .click(); // Click hamburger button await page.locator('[title="More options"]').click(); // Click 'Remove' and press OK await page.locator('li[role="menuitem"]:has-text("Remove")').click(); await page.locator('button:has-text("OK")').click(); //Expect Unnamed Condition Set to be removed in Main View const numberOfConditionSetsAtEnd = await page .locator('a:has-text("Unnamed Condition Set Condition Set")') .count(); expect(numberOfConditionSetsAtEnd).toEqual(numberOfConditionSetsToStart - 1); //Feature? //Domain Object is still available by direct URL after delete await page.goto(conditionSetUrl, { waitUntil: 'networkidle' }); await expect(page.locator('.l-browse-bar__object-name')).toContainText('Unnamed Condition Set'); }); }); test.describe('Basic Condition Set Use', () => { test.beforeEach(async ({ page }) => { // Open a browser, navigate to the main page, and wait until all network events to resolve await page.goto('./', { waitUntil: 'domcontentloaded' }); }); test('Can add a condition', async ({ page }) => { // Create a new condition set await createDomainObjectWithDefaults(page, { type: 'Condition Set', name: 'Test Condition Set' }); // Change the object to edit mode await page.locator('[title="Edit"]').click(); // Click Add Condition button await page.locator('#addCondition').click(); // Check that the new Unnamed Condition section appears const numOfUnnamedConditions = await page.locator('text=Unnamed Condition').count(); expect(numOfUnnamedConditions).toEqual(1); }); test('ConditionSet should display appropriate view options', async ({ page }) => { test.info().annotations.push({ type: 'issue', description: 'https://github.com/nasa/openmct/issues/5924' }); await createDomainObjectWithDefaults(page, { type: 'Sine Wave Generator', name: 'Alpha Sine Wave Generator' }); await createDomainObjectWithDefaults(page, { type: 'Sine Wave Generator', name: 'Beta Sine Wave Generator' }); const conditionSet1 = await createDomainObjectWithDefaults(page, { type: 'Condition Set', name: 'Test Condition Set' }); // Change the object to edit mode await page.locator('[title="Edit"]').click(); // Expand the 'My Items' folder in the left tree await page.goto(conditionSet1.url); page.click('button[title="Show selected item in tree"]'); // Add the Alpha & Beta Sine Wave Generator to the Condition Set and save changes const treePane = page.getByRole('tree', { name: 'Main Tree' }); const alphaGeneratorTreeItem = treePane.getByRole('treeitem', { name: 'Alpha Sine Wave Generator' }); const betaGeneratorTreeItem = treePane.getByRole('treeitem', { name: 'Beta Sine Wave Generator' }); const conditionCollection = page.locator('#conditionCollection'); await alphaGeneratorTreeItem.dragTo(conditionCollection); await betaGeneratorTreeItem.dragTo(conditionCollection); const saveButtonLocator = page.locator('button[title="Save"]'); await saveButtonLocator.click(); await page.getByRole('listitem', { name: 'Save and Finish Editing' }).click(); await page.click('button[title="Change the current view"]'); await expect(page.getByRole('menuitem', { name: /Lad Table/ })).toBeHidden(); await expect(page.getByRole('menuitem', { name: /Conditions View/ })).toBeVisible(); await expect(page.getByRole('menuitem', { name: /Plot/ })).toBeVisible(); await expect(page.getByRole('menuitem', { name: /Telemetry Table/ })).toBeVisible(); }); test('ConditionSet should output blank instead of the default value', async ({ page }) => { //Navigate to baseURL await page.goto('./', { waitUntil: 'domcontentloaded' }); //Click the Create button await page.click('button:has-text("Create")'); // Click the object specified by 'type' await page.click(`li[role='menuitem']:text("Sine Wave Generator")`); await page.getByRole('spinbutton', { name: 'Loading Delay (ms)' }).fill('8000'); const nameInput = page.locator('form[name="mctForm"] .first input[type="text"]'); await nameInput.fill('Delayed Sine Wave Generator'); // Click OK button and wait for Navigate event await Promise.all([ page.waitForLoadState(), page.click('[aria-label="Save"]'), // Wait for Save Banner to appear page.waitForSelector('.c-message-banner__message') ]); // Create a new condition set await createDomainObjectWithDefaults(page, { type: 'Condition Set', name: 'Test Blank Output of Condition Set' }); // Change the object to edit mode await page.locator('[title="Edit"]').click(); // Click Add Condition button twice await page.locator('#addCondition').click(); await page.locator('#addCondition').click(); await page.locator('#conditionCollection').getByRole('textbox').nth(0).fill('First Condition'); await page.locator('#conditionCollection').getByRole('textbox').nth(1).fill('Second Condition'); // Expand the 'My Items' folder in the left tree await page.locator('.c-tree__item__view-control.c-disclosure-triangle').first().click(); // Add the Sine Wave Generator to the Condition Set and save changes const treePane = page.getByRole('tree', { name: 'Main Tree' }); const sineWaveGeneratorTreeItem = treePane.getByRole('treeitem', { name: 'Delayed Sine Wave Generator' }); const conditionCollection = await page.locator('#conditionCollection'); await sineWaveGeneratorTreeItem.dragTo(conditionCollection); const firstCriterionTelemetry = await page.locator( '[aria-label="Criterion Telemetry Selection"] >> nth=0' ); firstCriterionTelemetry.selectOption({ label: 'Delayed Sine Wave Generator' }); const secondCriterionTelemetry = await page.locator( '[aria-label="Criterion Telemetry Selection"] >> nth=1' ); secondCriterionTelemetry.selectOption({ label: 'Delayed Sine Wave Generator' }); const firstCriterionMetadata = await page.locator( '[aria-label="Criterion Metadata Selection"] >> nth=0' ); firstCriterionMetadata.selectOption({ label: 'Sine' }); const secondCriterionMetadata = await page.locator( '[aria-label="Criterion Metadata Selection"] >> nth=1' ); secondCriterionMetadata.selectOption({ label: 'Sine' }); const firstCriterionComparison = await page.locator( '[aria-label="Criterion Comparison Selection"] >> nth=0' ); firstCriterionComparison.selectOption({ label: 'is greater than or equal to' }); const secondCriterionComparison = await page.locator( '[aria-label="Criterion Comparison Selection"] >> nth=1' ); secondCriterionComparison.selectOption({ label: 'is less than' }); const firstCriterionInput = await page.locator('[aria-label="Criterion Input"] >> nth=0'); await firstCriterionInput.fill('0'); const secondCriterionInput = await page.locator('[aria-label="Criterion Input"] >> nth=1'); await secondCriterionInput.fill('0'); const saveButtonLocator = page.locator('button[title="Save"]'); await saveButtonLocator.click(); await page.getByRole('listitem', { name: 'Save and Finish Editing' }).click(); const outputValue = await page.locator('[aria-label="Current Output Value"]'); await expect(outputValue).toHaveText('---'); }); });