Skip to content

Commit

Permalink
fix: DisplayLayout and FlexibleLayout toolbar actions only apply to s…
Browse files Browse the repository at this point in the history
…elected layout (#7184)

* refactor: convert to ES6 function

* fix: include `keyString` in event name

- This negates the need for complicated logic in determining which objectView the action was intended for

* fix: handle the case of currentView being null

* fix: add keyString to flexibleLayout toolbar events

* fix: properly unregister listeners

* fix: remove unused imports

* fix: revert parameter reorder

* refactor: replace usage of `arguments` with `...args`

* fix: add a11y to display layout + toolbar

* test: add first cut of layout toolbar suite

* test: cleanup a bit and add Image test

* test: add stubs

* fix: remove unused variable

* refactor(DisplayLayoutToolbar): convert to ES6 class

* test: generate localStorage data for display layout tests

* fix: clarify "Add" button label

* test: cleanup and don't parameterize tests

* test: fix path for recycled_local_storage.json

* fix: path to local storage file

* docs: add documentation for
utilizing localStorage in e2e

* fix: path to recycled_local_storage.json

* docs: add note hyperlink
  • Loading branch information
ozyx committed Nov 2, 2023
1 parent bdff210 commit 02f1013
Show file tree
Hide file tree
Showing 18 changed files with 1,121 additions and 804 deletions.
24 changes: 23 additions & 1 deletion e2e/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -193,7 +193,7 @@ Current list of test tags:
|`@ipad` | Test case or test suite is compatible with Playwright's iPad support and Open MCT's read-only mobile view (i.e. no create button).|
|`@gds` | Denotes a GDS Test Case used in the VIPER Mission.|
|`@addInit` | Initializes the browser with an injected and artificial state. Useful for loading non-default plugins. Likely will not work outside of `npm start`.|
|`@localStorage` | Captures or generates session storage to manipulate browser state. Useful for excluding in tests which require a persistent backend (i.e. CouchDB).|
|`@localStorage` | Captures or generates session storage to manipulate browser state. Useful for excluding in tests which require a persistent backend (i.e. CouchDB). See [note](#utilizing-localstorage)|
|`@snapshot` | Uses Playwright's snapshot functionality to record a copy of the DOM for direct comparison. Must be run inside of the playwright container.|
|`@unstable` | A new test or test which is known to be flaky.|
|`@2p` | Indicates that multiple users are involved, or multiple tabs/pages are used. Useful for testing multi-user interactivity.|
Expand Down Expand Up @@ -352,6 +352,28 @@ By adhering to this principle, we can create tests that are both robust and refl
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.
##### 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.
1. To generate a localStorage state to be used in a test:
- Add an e2e test to our generateLocalStorageData suite which sets the initial state (creating/configuring objects, etc.), saving it in the `test-data` folder:
```js
// Save localStorage for future test execution
await context.storageState({
path: path.join(__dirname, '../../../e2e/test-data/display_layout_with_child_layouts.json')
});
```
- Load the state from file at the beginning of the desired test suite (within the `test.describe()`). (NOTE: the storage state will be used for each test in the suite, so you may need to create a new suite):
```js
const LOCALSTORAGE_PATH = path.resolve(
__dirname,
'../../../../test-data/display_layout_with_child_layouts.json'
);
test.use({
storageState: path.resolve(__dirname, LOCALSTORAGE_PATH)
});
```
### How to write a great test
- Avoid using css locators to find elements to the page. Use modern web accessible locators like `getByRole`
Expand Down
22 changes: 22 additions & 0 deletions e2e/test-data/display_layout_with_child_layouts.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
{
"cookies": [],
"origins": [
{
"origin": "http://localhost:8080",
"localStorage": [
{
"name": "mct",
"value": "{\"mine\":{\"identifier\":{\"key\":\"mine\",\"namespace\":\"\"},\"name\":\"My Items\",\"type\":\"folder\",\"composition\":[{\"key\":\"764a490f-4a83-4874-a062-e38c112f69c7\",\"namespace\":\"\"}],\"location\":\"ROOT\",\"modified\":1732413602020,\"created\":1732413601160,\"persisted\":1732413602020},\"764a490f-4a83-4874-a062-e38c112f69c7\":{\"identifier\":{\"key\":\"764a490f-4a83-4874-a062-e38c112f69c7\",\"namespace\":\"\"},\"name\":\"Parent Display Layout\",\"type\":\"layout\",\"composition\":[{\"key\":\"801d3a35-91ac-43ae-b175-bc1be65f3587\",\"namespace\":\"\"},{\"key\":\"d70f3dfc-99c6-47f6-87c7-db8faed8598b\",\"namespace\":\"\"}],\"configuration\":{\"items\":[{\"width\":32,\"height\":18,\"x\":1,\"y\":30,\"identifier\":{\"key\":\"801d3a35-91ac-43ae-b175-bc1be65f3587\",\"namespace\":\"\"},\"hasFrame\":true,\"fontSize\":\"default\",\"font\":\"default\",\"type\":\"subobject-view\",\"id\":\"9acce141-5291-427d-8785-847faa2707e6\"},{\"width\":32,\"height\":18,\"x\":30,\"y\":1,\"identifier\":{\"key\":\"d70f3dfc-99c6-47f6-87c7-db8faed8598b\",\"namespace\":\"\"},\"hasFrame\":true,\"fontSize\":\"default\",\"font\":\"default\",\"type\":\"subobject-view\",\"id\":\"9f89d6f6-fb7f-4af3-9cc3-4c5d0864e908\"}],\"layoutGrid\":[10,10]},\"notes\":\"framework/generateLocalStorageData.e2e.spec.js\\nGenerate Visual Test Data @localStorage @generatedata\\nGenerate display layout with 2 child display layouts\\nchrome\",\"modified\":1732413605120,\"location\":\"mine\",\"created\":1732413602020,\"persisted\":1732413605120},\"801d3a35-91ac-43ae-b175-bc1be65f3587\":{\"name\":\"Child Layout 1\",\"type\":\"layout\",\"identifier\":{\"key\":\"801d3a35-91ac-43ae-b175-bc1be65f3587\",\"namespace\":\"\"},\"composition\":[],\"configuration\":{\"items\":[],\"layoutGrid\":[10,10]},\"notes\":\"framework/generateLocalStorageData.e2e.spec.js\\nGenerate Visual Test Data @localStorage @generatedata\\nGenerate display layout with 2 child display layouts\\nchrome\",\"modified\":1732413603140,\"location\":\"764a490f-4a83-4874-a062-e38c112f69c7\",\"created\":1732413603140,\"persisted\":1732413603140},\"d70f3dfc-99c6-47f6-87c7-db8faed8598b\":{\"name\":\"Child Layout 2\",\"type\":\"layout\",\"identifier\":{\"key\":\"d70f3dfc-99c6-47f6-87c7-db8faed8598b\",\"namespace\":\"\"},\"composition\":[],\"configuration\":{\"items\":[],\"layoutGrid\":[10,10]},\"notes\":\"framework/generateLocalStorageData.e2e.spec.js\\nGenerate Visual Test Data @localStorage @generatedata\\nGenerate display layout with 2 child display layouts\\nchrome\",\"modified\":1732413604240,\"location\":\"764a490f-4a83-4874-a062-e38c112f69c7\",\"created\":1732413604240,\"persisted\":1732413604240}}"
},
{
"name": "mct-recent-objects",
"value": "[{\"objectPath\":[{\"identifier\":{\"key\":\"764a490f-4a83-4874-a062-e38c112f69c7\",\"namespace\":\"\"},\"name\":\"Parent Display Layout\",\"type\":\"layout\",\"composition\":[{\"key\":\"801d3a35-91ac-43ae-b175-bc1be65f3587\",\"namespace\":\"\"},{\"key\":\"d70f3dfc-99c6-47f6-87c7-db8faed8598b\",\"namespace\":\"\"}],\"configuration\":{\"items\":[{\"width\":32,\"height\":18,\"x\":1,\"y\":1,\"identifier\":{\"key\":\"801d3a35-91ac-43ae-b175-bc1be65f3587\",\"namespace\":\"\"},\"hasFrame\":true,\"fontSize\":\"default\",\"font\":\"default\",\"type\":\"subobject-view\",\"id\":\"9acce141-5291-427d-8785-847faa2707e6\"}],\"layoutGrid\":[10,10]},\"notes\":\"framework/generateLocalStorageData.e2e.spec.js\\nGenerate Visual Test Data @localStorage @generatedata\\nGenerate display layout with 2 child display layouts\\nchrome\",\"modified\":1732413604240,\"location\":\"mine\",\"created\":1732413602020,\"persisted\":1732413604240},{\"identifier\":{\"key\":\"mine\",\"namespace\":\"\"},\"name\":\"My Items\",\"type\":\"folder\",\"composition\":[{\"key\":\"764a490f-4a83-4874-a062-e38c112f69c7\",\"namespace\":\"\"}],\"location\":\"ROOT\",\"modified\":1732413602020,\"created\":1732413601160,\"persisted\":1732413602020},{\"identifier\":{\"key\":\"ROOT\",\"namespace\":\"\"},\"name\":\"Open MCT\",\"type\":\"root\",\"composition\":[{\"key\":\"mine\",\"namespace\":\"\"}]}],\"navigationPath\":\"/browse/mine/764a490f-4a83-4874-a062-e38c112f69c7\",\"domainObject\":{\"identifier\":{\"key\":\"764a490f-4a83-4874-a062-e38c112f69c7\",\"namespace\":\"\"},\"name\":\"Parent Display Layout\",\"type\":\"layout\",\"composition\":[{\"key\":\"801d3a35-91ac-43ae-b175-bc1be65f3587\",\"namespace\":\"\"},{\"key\":\"d70f3dfc-99c6-47f6-87c7-db8faed8598b\",\"namespace\":\"\"}],\"configuration\":{\"items\":[{\"width\":32,\"height\":18,\"x\":1,\"y\":1,\"identifier\":{\"key\":\"801d3a35-91ac-43ae-b175-bc1be65f3587\",\"namespace\":\"\"},\"hasFrame\":true,\"fontSize\":\"default\",\"font\":\"default\",\"type\":\"subobject-view\",\"id\":\"9acce141-5291-427d-8785-847faa2707e6\"}],\"layoutGrid\":[10,10]},\"notes\":\"framework/generateLocalStorageData.e2e.spec.js\\nGenerate Visual Test Data @localStorage @generatedata\\nGenerate display layout with 2 child display layouts\\nchrome\",\"modified\":1732413604240,\"location\":\"mine\",\"created\":1732413602020,\"persisted\":1732413604240}},{\"objectPath\":[{\"identifier\":{\"key\":\"d70f3dfc-99c6-47f6-87c7-db8faed8598b\",\"namespace\":\"\"},\"name\":\"Child Layout 2\",\"type\":\"layout\",\"composition\":[],\"configuration\":{\"items\":[],\"layoutGrid\":[10,10]},\"notes\":\"framework/generateLocalStorageData.e2e.spec.js\\nGenerate Visual Test Data @localStorage @generatedata\\nGenerate display layout with 2 child display layouts\\nchrome\",\"modified\":1732413604240,\"location\":\"764a490f-4a83-4874-a062-e38c112f69c7\",\"created\":1732413604240,\"persisted\":1732413604240},{\"identifier\":{\"key\":\"764a490f-4a83-4874-a062-e38c112f69c7\",\"namespace\":\"\"},\"name\":\"Parent Display Layout\",\"type\":\"layout\",\"composition\":[{\"key\":\"801d3a35-91ac-43ae-b175-bc1be65f3587\",\"namespace\":\"\"},{\"key\":\"d70f3dfc-99c6-47f6-87c7-db8faed8598b\",\"namespace\":\"\"}],\"configuration\":{\"items\":[{\"width\":32,\"height\":18,\"x\":1,\"y\":1,\"identifier\":{\"key\":\"801d3a35-91ac-43ae-b175-bc1be65f3587\",\"namespace\":\"\"},\"hasFrame\":true,\"fontSize\":\"default\",\"font\":\"default\",\"type\":\"subobject-view\",\"id\":\"9acce141-5291-427d-8785-847faa2707e6\"}],\"layoutGrid\":[10,10]},\"notes\":\"framework/generateLocalStorageData.e2e.spec.js\\nGenerate Visual Test Data @localStorage @generatedata\\nGenerate display layout with 2 child display layouts\\nchrome\",\"modified\":1732413604240,\"location\":\"mine\",\"created\":1732413602020,\"persisted\":1732413604240},{\"identifier\":{\"key\":\"mine\",\"namespace\":\"\"},\"name\":\"My Items\",\"type\":\"folder\",\"composition\":[{\"key\":\"764a490f-4a83-4874-a062-e38c112f69c7\",\"namespace\":\"\"}],\"location\":\"ROOT\",\"modified\":1732413602020,\"created\":1732413601160,\"persisted\":1732413602020},{\"identifier\":{\"key\":\"ROOT\",\"namespace\":\"\"},\"name\":\"Open MCT\",\"type\":\"root\",\"composition\":[{\"key\":\"mine\",\"namespace\":\"\"}]}],\"navigationPath\":\"/browse/mine/764a490f-4a83-4874-a062-e38c112f69c7/d70f3dfc-99c6-47f6-87c7-db8faed8598b\",\"domainObject\":{\"identifier\":{\"key\":\"d70f3dfc-99c6-47f6-87c7-db8faed8598b\",\"namespace\":\"\"},\"name\":\"Child Layout 2\",\"type\":\"layout\",\"composition\":[],\"configuration\":{\"items\":[],\"layoutGrid\":[10,10]},\"notes\":\"framework/generateLocalStorageData.e2e.spec.js\\nGenerate Visual Test Data @localStorage @generatedata\\nGenerate display layout with 2 child display layouts\\nchrome\",\"modified\":1732413604240,\"location\":\"764a490f-4a83-4874-a062-e38c112f69c7\",\"created\":1732413604240,\"persisted\":1732413604240}},{\"objectPath\":[{\"identifier\":{\"key\":\"801d3a35-91ac-43ae-b175-bc1be65f3587\",\"namespace\":\"\"},\"name\":\"Child Layout 1\",\"type\":\"layout\",\"composition\":[],\"configuration\":{\"items\":[],\"layoutGrid\":[10,10]},\"notes\":\"framework/generateLocalStorageData.e2e.spec.js\\nGenerate Visual Test Data @localStorage @generatedata\\nGenerate display layout with 2 child display layouts\\nchrome\",\"modified\":1732413603140,\"location\":\"764a490f-4a83-4874-a062-e38c112f69c7\",\"created\":1732413603140,\"persisted\":1732413603140},{\"identifier\":{\"key\":\"764a490f-4a83-4874-a062-e38c112f69c7\",\"namespace\":\"\"},\"name\":\"Parent Display Layout\",\"type\":\"layout\",\"composition\":[{\"key\":\"801d3a35-91ac-43ae-b175-bc1be65f3587\",\"namespace\":\"\"},{\"key\":\"d70f3dfc-99c6-47f6-87c7-db8faed8598b\",\"namespace\":\"\"}],\"configuration\":{\"items\":[{\"width\":32,\"height\":18,\"x\":1,\"y\":1,\"identifier\":{\"key\":\"801d3a35-91ac-43ae-b175-bc1be65f3587\",\"namespace\":\"\"},\"hasFrame\":true,\"fontSize\":\"default\",\"font\":\"default\",\"type\":\"subobject-view\",\"id\":\"9acce141-5291-427d-8785-847faa2707e6\"}],\"layoutGrid\":[10,10]},\"notes\":\"framework/generateLocalStorageData.e2e.spec.js\\nGenerate Visual Test Data @localStorage @generatedata\\nGenerate display layout with 2 child display layouts\\nchrome\",\"modified\":1732413604240,\"location\":\"mine\",\"created\":1732413602020,\"persisted\":1732413604240},{\"identifier\":{\"key\":\"mine\",\"namespace\":\"\"},\"name\":\"My Items\",\"type\":\"folder\",\"composition\":[{\"key\":\"764a490f-4a83-4874-a062-e38c112f69c7\",\"namespace\":\"\"}],\"location\":\"ROOT\",\"modified\":1732413602020,\"created\":1732413601160,\"persisted\":1732413602020},{\"identifier\":{\"key\":\"ROOT\",\"namespace\":\"\"},\"name\":\"Open MCT\",\"type\":\"root\",\"composition\":[{\"key\":\"mine\",\"namespace\":\"\"}]}],\"navigationPath\":\"/browse/mine/764a490f-4a83-4874-a062-e38c112f69c7/801d3a35-91ac-43ae-b175-bc1be65f3587\",\"domainObject\":{\"identifier\":{\"key\":\"801d3a35-91ac-43ae-b175-bc1be65f3587\",\"namespace\":\"\"},\"name\":\"Child Layout 1\",\"type\":\"layout\",\"composition\":[],\"configuration\":{\"items\":[],\"layoutGrid\":[10,10]},\"notes\":\"framework/generateLocalStorageData.e2e.spec.js\\nGenerate Visual Test Data @localStorage @generatedata\\nGenerate display layout with 2 child display layouts\\nchrome\",\"modified\":1732413603140,\"location\":\"764a490f-4a83-4874-a062-e38c112f69c7\",\"created\":1732413603140,\"persisted\":1732413603140}},{\"objectPath\":[{\"identifier\":{\"key\":\"mine\",\"namespace\":\"\"},\"name\":\"My Items\",\"type\":\"folder\",\"composition\":[{\"key\":\"764a490f-4a83-4874-a062-e38c112f69c7\",\"namespace\":\"\"}],\"location\":\"ROOT\",\"modified\":1732413602020,\"created\":1732413601160,\"persisted\":1732413602020},{\"identifier\":{\"key\":\"ROOT\",\"namespace\":\"\"},\"name\":\"Open MCT\",\"type\":\"root\",\"composition\":[{\"key\":\"mine\",\"namespace\":\"\"}]}],\"navigationPath\":\"/browse/mine\",\"domainObject\":{\"identifier\":{\"key\":\"mine\",\"namespace\":\"\"},\"name\":\"My Items\",\"type\":\"folder\",\"composition\":[{\"key\":\"764a490f-4a83-4874-a062-e38c112f69c7\",\"namespace\":\"\"}],\"location\":\"ROOT\",\"modified\":1732413602020,\"created\":1732413601160,\"persisted\":1732413602020}}]"
},
{
"name": "mct-tree-expanded",
"value": "[]"
}
]
}
]
}
36 changes: 36 additions & 0 deletions e2e/tests/framework/generateLocalStorageData.e2e.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,42 @@ test.describe('Generate Visual Test Data @localStorage @generatedata', () => {
await page.goto('./', { waitUntil: 'domcontentloaded' });
});

test('Generate display layout with 2 child display layouts', async ({ page, context }) => {
// Create Display Layout
const parent = await createDomainObjectWithDefaults(page, {
type: 'Display Layout',
name: 'Parent Display Layout'
});
const child1 = await createDomainObjectWithDefaults(page, {
type: 'Display Layout',
name: 'Child Layout 1',
parent: parent.uuid
});
const child2 = await createDomainObjectWithDefaults(page, {
type: 'Display Layout',
name: 'Child Layout 2',
parent: parent.uuid
});

await page.goto(parent.url);
await page.getByLabel('Edit').click();
await page.getByLabel(`${child2.name} Layout Grid`).hover();
await page.getByLabel('Move Sub-object Frame').nth(1).click();
await page.getByLabel('X:').fill('30');

await page.getByLabel(`${child1.name} Layout Grid`).hover();
await page.getByLabel('Move Sub-object Frame').first().click();
await page.getByLabel('Y:').fill('30');

await page.getByLabel('Save').click();
await page.getByRole('listitem', { name: 'Save and Finish Editing' }).click();

//Save localStorage for future test execution
await context.storageState({
path: path.join(__dirname, '../../../e2e/test-data/display_layout_with_child_layouts.json')
});
});

// TODO: Visual test for the generated object here
// - Move to using appActions to create the overlay plot
// and embedded standard telemetry object
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@
* 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 tests which verify the basic operations surrounding conditionSets. Note: this
suite is sharing state between tests which is considered an anti-pattern. Implementing in this way to
Expand All @@ -31,6 +31,7 @@ const {
createDomainObjectWithDefaults,
createExampleTelemetryObject
} = require('../../../../appActions');
const path = require('path');

let conditionSetUrl;
let getConditionSetIdentifierFromUrl;
Expand All @@ -48,7 +49,9 @@ test.describe.serial('Condition Set CRUD Operations on @localStorage', () => {
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' });
await context.storageState({
path: path.resolve(__dirname, '../../../../test-data/recycled_local_storage.json')
});

//Set object identifier from url
conditionSetUrl = page.url();
Expand All @@ -59,7 +62,9 @@ test.describe.serial('Condition Set CRUD Operations on @localStorage', () => {
});

//Load localStorage for subsequent tests
test.use({ storageState: './e2e/test-data/recycled_local_storage.json' });
test.use({
storageState: path.resolve(__dirname, '../../../../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 ({
Expand Down

0 comments on commit 02f1013

Please sign in to comment.