Fixes for e2e tests following the Vue 3 compat upgrade (#6837)

* clock, timeConductor and appActions fixes

* Ensure realtime uses upstream context when available
Eliminate ambiguity when looking for time conductor locator

* Fix log plot e2e tests

* Fix displayLayout e2e tests

* Specify global time conductor to fix issues with duplicate selectors with independent time contexts

* a11y: ARIA for conductor and independent time conductor

* a11y: fix label collisions, specify 'Menu' in label

* Add watch mode

* fix(e2e): update appActions and tests to use a11y locators for ITC

* Don't remove the itc popup from the DOM. Just show/hide it once it's added the first time.

* test(e2e): disable one imagery test due to known bug

* Add fixme to tagging tests, issue described in 6822

* Fix locator for time conductor popups

* Improve how time bounds are set in independent time conductor.
Fix tests for flexible layout and timestrip

* Fix some tests for itc for display layouts

* Fix Inspector tabs remounting on change

* fix autoscale test and snapshot

* Fix telemetry table test

* Fix timestrip test

* e2e: move test info annotations to within test

* 6826: Fixes padStart error due to using it on a number rather than a string

* fix(e2e): update snapshots

* fix(e2e): fix restricted notebook locator

* fix(restrictedNotebook): fix issue causing sections not to update on lock

* fix(restrictedNotebook): fix issue causing snapshots to not be able to be deleted from a locked page

- Using `this.$delete(arr, index)` does not update the `length` property on the underlying target object, so it can lead to bizarre issues where your array is of length 4 but it has 3 objects in it.

* fix: replace all instances of `$delete` with `Array.splice()` or `delete`

* fix(e2e): fix grand search test

* fix(#3117): can remove item from displayLayout via tree context menu while viewing another item

* fix: remove typo 

* Wait for background image to load

* fix(#6832): timelist events can tick down

* fix: ensure that menuitems have the raw objects so emits work

* fix: assign new arrays instead of editing state in-place

* refactor(timelist): use `getClock()` instead of `clock()`

* Revert "refactor(timelist): use `getClock()` instead of `clock()`"

This reverts commit d888553112.

* refactor(timelist): use new timeAPI

* Stop ticking when the independent time context is disabled (#6833)

* Turn off the clock ticket for independent time conductor when it is disabled

* Fix linting issues

---------

Co-authored-by: Khalid Adil <khalidadil29@gmail.com>

* test: update couchdb notebook test

* fix: codeQL warnings

* fix(tree-item): infinite spinner issue

- Using `indexOf()` with an object was failing due to some items in the tree being Proxy-wrapped and others not. So instead, use `findIndex()` with a predicate that compares the navigationPaths of both objects

* [Timer] Remove "refresh" call, it is not needed (#6841)

* removing an unneccessary refresh that waas causing many get requests
* lets just pretend this never happened

* fix(mct-tree): maintain reactivity of all tree items

* Hide change role button in the indicator in cases where there is only… (#6840)

Hide change role button in the indicator in cases where there is only a single role available for the current user

---------

Co-authored-by: Shefali <simplyrender@gmail.com>
Co-authored-by: Khalid Adil <khalidadil29@gmail.com>
Co-authored-by: John Hill <john.c.hill@nasa.gov>
Co-authored-by: David Tsay <david.e.tsay@nasa.gov>
Co-authored-by: Jamie V <jamie.j.vigliotta@nasa.gov>
This commit is contained in:
Jesse Mazzella
2023-07-27 19:06:41 -07:00
committed by GitHub
parent 4885c816dc
commit 16e1ac2529
96 changed files with 921 additions and 816 deletions

View File

@@ -41,7 +41,7 @@ test.describe('Clock Generator CRUD Operations', () => {
await page.click('button:has-text("Create")');
// Click Clock
await page.click('text=Clock');
await page.getByRole('menuitem').first().click();
// Click .icon-arrow-down
await page.locator('.icon-arrow-down').click();

View File

@@ -25,7 +25,8 @@ const {
createDomainObjectWithDefaults,
setStartOffset,
setFixedTimeMode,
setRealTimeMode
setRealTimeMode,
setIndependentTimeConductorBounds
} = require('../../../../appActions');
test.describe('Display Layout', () => {
@@ -231,20 +232,27 @@ test.describe('Display Layout', () => {
let layoutGridHolder = page.locator('.l-layout__grid-holder');
await exampleImageryTreeItem.dragTo(layoutGridHolder);
//adjust so that we can see the independent time conductor toggle
// Adjust object height
await page.locator('div[title="Resize object height"] > input').click();
await page.locator('div[title="Resize object height"] > input').fill('70');
// Adjust object width
await page.locator('div[title="Resize object width"] > input').click();
await page.locator('div[title="Resize object width"] > input').fill('70');
await page.locator('button[title="Save"]').click();
await page.locator('text=Save and Finish Editing').click();
// flip on independent time conductor
await page.getByTitle('Enable independent Time Conductor').first().locator('label').click();
await page.getByRole('textbox').nth(1).fill('2021-12-30 01:11:00.000Z');
await page.getByRole('textbox').nth(0).fill('2021-12-30 01:01:00.000Z');
await page.getByRole('textbox').nth(1).click();
const startDate = '2021-12-30 01:01:00.000Z';
const endDate = '2021-12-30 01:11:00.000Z';
await setIndependentTimeConductorBounds(page, startDate, endDate);
// check image date
await expect(page.getByText('2021-12-30 01:11:00.000Z').first()).toBeVisible();
// flip it off
await page.getByTitle('Disable independent Time Conductor').first().locator('label').click();
await page.getByRole('switch').click();
// timestamp shouldn't be in the past anymore
await expect(page.getByText('2021-12-30 01:11:00.000Z')).toBeHidden();
});

View File

@@ -21,7 +21,10 @@
*****************************************************************************/
const { test, expect } = require('../../../../pluginFixtures');
const { createDomainObjectWithDefaults } = require('../../../../appActions');
const {
createDomainObjectWithDefaults,
setIndependentTimeConductorBounds
} = require('../../../../appActions');
test.describe('Flexible Layout', () => {
let sineWaveObject;
@@ -187,16 +190,17 @@ test.describe('Flexible Layout', () => {
await page.locator('text=Save and Finish Editing').click();
// flip on independent time conductor
await page.getByTitle('Enable independent Time Conductor').first().locator('label').click();
await page.getByRole('textbox').nth(1).fill('2021-12-30 01:11:00.000Z');
await page.getByRole('textbox').nth(0).fill('2021-12-30 01:01:00.000Z');
await page.getByRole('textbox').nth(1).click();
await setIndependentTimeConductorBounds(
page,
'2021-12-30 01:01:00.000Z',
'2021-12-30 01:11:00.000Z'
);
// check image date
await expect(page.getByText('2021-12-30 01:11:00.000Z').first()).toBeVisible();
// flip it off
await page.getByTitle('Disable independent Time Conductor').first().locator('label').click();
await page.getByRole('switch').click();
// timestamp shouldn't be in the past anymore
await expect(page.getByText('2021-12-30 01:11:00.000Z')).toBeHidden();
});

View File

@@ -27,7 +27,7 @@ but only assume that example imagery is present.
/* globals process */
const { waitForAnimations } = require('../../../../baseFixtures');
const { test, expect } = require('../../../../pluginFixtures');
const { createDomainObjectWithDefaults } = require('../../../../appActions');
const { createDomainObjectWithDefaults, setRealTimeMode } = require('../../../../appActions');
const backgroundImageSelector = '.c-imagery__main-image__background-image';
const panHotkey = process.platform === 'linux' ? ['Shift', 'Alt'] : ['Alt'];
const tagHotkey = ['Shift', 'Alt'];
@@ -46,6 +46,7 @@ test.describe('Example Imagery Object', () => {
// Verify that the created object is focused
await expect(page.locator('.l-browse-bar__object-name')).toContainText(exampleImagery.name);
await page.locator('.c-imagery__main-image__bg').hover({ trial: true });
await page.locator(backgroundImageSelector).waitFor();
});
test('Can use Mouse Wheel to zoom in and out of latest image', async ({ page }) => {
@@ -71,46 +72,60 @@ test.describe('Example Imagery Object', () => {
});
test('Can use independent time conductor to change time', async ({ page }) => {
test.info().annotations.push({
type: 'issue',
description: 'https://github.com/nasa/openmct/issues/6821'
});
// Test independent fixed time with global fixed time
// flip on independent time conductor
await page.getByTitle('Enable independent Time Conductor').locator('label').click();
await page.getByRole('textbox').nth(1).fill('2021-12-30 01:11:00.000Z');
await page.getByRole('textbox').nth(0).fill('2021-12-30 01:01:00.000Z');
await page.getByRole('textbox').nth(1).click();
await page.getByRole('switch', { name: 'Enable Independent Time Conductor' }).click();
await page.getByRole('button', { name: 'Independent Time Conductor Settings' }).click();
await page.getByRole('textbox', { name: 'Start date' }).click();
await page.getByRole('textbox', { name: 'Start date' }).fill('');
await page.getByRole('textbox', { name: 'Start date' }).fill('2021-12-30');
await page.getByRole('textbox', { name: 'Start time' }).click();
await page.getByRole('textbox', { name: 'Start time' }).fill('');
await page.getByRole('textbox', { name: 'Start time' }).fill('01:01:00');
await page.getByRole('textbox', { name: 'End date' }).click();
await page.getByRole('textbox', { name: 'End date' }).fill('');
await page.getByRole('textbox', { name: 'End date' }).fill('2021-12-30');
await page.getByRole('textbox', { name: 'End time' }).click();
await page.getByRole('textbox', { name: 'End time' }).fill('');
await page.getByRole('textbox', { name: 'End time' }).fill('01:11:00');
await page.getByRole('button', { name: 'Submit time bounds' }).click();
// check image date
await expect(page.getByText('2021-12-30 01:11:00.000Z').first()).toBeVisible();
// flip it off
await page.getByTitle('Disable independent Time Conductor').locator('label').click();
await page.getByRole('switch', { name: 'Disable Independent Time Conductor' }).click();
// timestamp shouldn't be in the past anymore
await expect(page.getByText('2021-12-30 01:11:00.000Z')).toBeHidden();
// Test independent fixed time with global realtime
await page.getByRole('button', { name: /Fixed Timespan/ }).click();
await page.getByTestId('conductor-modeOption-realtime').click();
await page.getByTitle('Enable independent Time Conductor').locator('label').click();
await setRealTimeMode(page);
await page.getByRole('switch', { name: 'Enable Independent Time Conductor' }).click();
// check image date to be in the past
await expect(page.getByText('2021-12-30 01:11:00.000Z').first()).toBeVisible();
// flip it off
await page.getByTitle('Disable independent Time Conductor').locator('label').click();
await page.getByRole('switch', { name: 'Disable Independent Time Conductor' }).click();
// timestamp shouldn't be in the past anymore
await expect(page.getByText('2021-12-30 01:11:00.000Z')).toBeHidden();
// Test independent realtime with global realtime
await page.getByTitle('Enable independent Time Conductor').locator('label').click();
await page.getByRole('switch', { name: 'Enable Independent Time Conductor' }).click();
// check image date
await expect(page.getByText('2021-12-30 01:11:00.000Z').first()).toBeVisible();
// change independent time to realtime
await page.getByRole('button', { name: /Fixed Timespan/ }).click();
await page.getByRole('menuitem', { name: /Local Clock/ }).click();
await page.getByRole('button', { name: 'Independent Time Conductor Settings' }).click();
await page.getByRole('button', { name: 'Independent Time Conductor Mode Menu' }).click();
await page.getByRole('menuitem', { name: /Real-Time/ }).click();
// timestamp shouldn't be in the past anymore
await expect(page.getByText('2021-12-30 01:11:00.000Z')).toBeHidden();
// back to the past
await page
.getByRole('button', { name: /Local Clock/ })
.first()
.click();
await page.getByRole('button', { name: 'Independent Time Conductor Mode Menu' }).click();
await page.getByRole('menuitem', { name: /Real-Time/ }).click();
await page.getByRole('button', { name: 'Independent Time Conductor Mode Menu' }).click();
await page.getByRole('menuitem', { name: /Fixed Timespan/ }).click();
// check image date to be in the past
await expect(page.getByText('2021-12-30 01:11:00.000Z').first()).toBeVisible();
@@ -247,7 +262,7 @@ test.describe('Example Imagery Object', () => {
test('Uses low fetch priority', async ({ page }) => {
const priority = await page.locator('.js-imageryView-image').getAttribute('fetchpriority');
await expect(priority).toBe('low');
expect(priority).toBe('low');
});
});
@@ -281,7 +296,7 @@ test.describe('Example Imagery in Display Layout', () => {
await setRealTimeMode(page);
// pause/play button
const pausePlayButton = await page.locator('.c-button.pause-play');
const pausePlayButton = page.locator('.c-button.pause-play');
await expect.soft(pausePlayButton).not.toHaveClass(/is-paused/);
@@ -304,7 +319,7 @@ test.describe('Example Imagery in Display Layout', () => {
await setRealTimeMode(page);
// pause/play button
const pausePlayButton = await page.locator('.c-button.pause-play');
const pausePlayButton = page.locator('.c-button.pause-play');
await pausePlayButton.click();
await expect.soft(pausePlayButton).toHaveClass(/is-paused/);
@@ -928,15 +943,3 @@ async function createImageryView(page) {
page.waitForSelector('.c-message-banner__message')
]);
}
/**
* @param {import('@playwright/test').Page} page
*/
async function setRealTimeMode(page) {
await page.locator('.c-compact-tc').click();
await page.waitForSelector('.c-tc-input-popup', { state: 'visible' });
// Click mode dropdown
await page.getByRole('button', { name: ' Fixed Timespan ' }).click();
// Click realtime
await page.getByTestId('conductor-modeOption-realtime').click();
}

View File

@@ -51,10 +51,9 @@ test.describe('Notebook Tests with CouchDB @couchdb', () => {
page.on('request', (request) => notebookElementsRequests.push(request));
//Clicking Add Page generates
let [notebookUrlRequest, allDocsRequest] = await Promise.all([
let [notebookUrlRequest] = await Promise.all([
// Waits for the next request with the specified url
page.waitForRequest(`**/openmct/${testNotebook.uuid}`),
page.waitForRequest('**/openmct/_all_docs?include_docs=true'),
// Triggers the request
page.click('[aria-label="Add Page"]')
]);
@@ -64,15 +63,13 @@ test.describe('Notebook Tests with CouchDB @couchdb', () => {
// Assert that only two requests are made
// Network Requests are:
// 1) The actual POST to create the page
// 2) The shared worker event from 👆 request
expect(notebookElementsRequests.length).toBe(2);
expect(notebookElementsRequests.length).toBe(1);
// Assert on request object
expect(notebookUrlRequest.postDataJSON().metadata.name).toBe(testNotebook.name);
expect(notebookUrlRequest.postDataJSON().model.persisted).toBeGreaterThanOrEqual(
notebookUrlRequest.postDataJSON().model.modified
);
expect(allDocsRequest.postDataJSON().keys).toContain(testNotebook.uuid);
// Add an entry
// Network Requests are:

View File

@@ -134,7 +134,7 @@ test.describe('Restricted Notebook with at least one entry and with the page loc
// Click the context menu button for the new page
await page.getByTitle('Open context menu').click();
// Delete the page
await page.getByRole('listitem', { name: 'Delete Page' }).click();
await page.getByRole('menuitem', { name: 'Delete Page' }).click();
// Click OK button
await page.getByRole('button', { name: 'Ok' }).click();

View File

@@ -24,7 +24,7 @@
Testsuite for plot autoscale.
*/
const { selectInspectorTab } = require('../../../../appActions');
const { selectInspectorTab, setTimeConductorBounds } = require('../../../../appActions');
const { test, expect } = require('../../../../pluginFixtures');
test.use({
viewport: {
@@ -107,7 +107,7 @@ test.describe('Autoscale', () => {
await page.keyboard.up('Alt');
// Ensure the drag worked.
await testYTicks(page, ['0.00', '0.50', '1.00', '1.50', '2.00', '2.50', '3.00', '3.50']);
await testYTicks(page, ['-0.50', '0.00', '0.50', '1.00', '1.50', '2.00', '2.50', '3.00']);
//Wait for canvas to stablize.
await canvas.hover({ trial: true });
@@ -131,12 +131,7 @@ async function setTimeRange(
// Set a specific time range for consistency, otherwise it will change
// on every test to a range based on the current time.
const timeInputs = page.locator('input.c-input--datetime');
await timeInputs.first().click();
await timeInputs.first().fill(start);
await timeInputs.nth(1).click();
await timeInputs.nth(1).fill(end);
await setTimeConductorBounds(page, start, end);
}
/**

Binary file not shown.

Before

Width:  |  Height:  |  Size: 19 KiB

After

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 17 KiB

After

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 19 KiB

After

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 19 KiB

After

Width:  |  Height:  |  Size: 20 KiB

View File

@@ -26,7 +26,7 @@ necessarily be used for reference when writing new tests in this area.
*/
const { test, expect } = require('../../../../pluginFixtures');
const { selectInspectorTab } = require('../../../../appActions');
const { selectInspectorTab, setTimeConductorBounds } = require('../../../../appActions');
test.describe('Log plot tests', () => {
test('Log Plot ticks are functionally correct in regular and log mode and after refresh', async ({
@@ -87,12 +87,10 @@ async function makeOverlayPlot(page, myItemsFolderName) {
// Set a specific time range for consistency, otherwise it will change
// on every test to a range based on the current time.
const timeInputs = page.locator('input.c-input--datetime');
await timeInputs.first().click();
await timeInputs.first().fill('2022-03-29 22:00:00.000Z');
const start = '2022-03-29 22:00:00.000Z';
const end = '2022-03-29 22:00:30.000Z';
await timeInputs.nth(1).click();
await timeInputs.nth(1).fill('2022-03-29 22:00:30.000Z');
await setTimeConductorBounds(page, start, end);
// create overlay plot

View File

@@ -32,7 +32,7 @@ const {
waitForPlotsToRender
} = require('../../../../appActions');
test.describe('Plot Tagging', () => {
test.describe.fixme('Plot Tagging', () => {
/**
* Given a canvas and a set of points, tags the points on the canvas.
* @param {import('@playwright/test').Page} page
@@ -167,6 +167,10 @@ test.describe('Plot Tagging', () => {
});
test('Tags work with Overlay Plots', async ({ page }) => {
test.info().annotations.push({
type: 'issue',
description: 'https://github.com/nasa/openmct/issues/6822'
});
//Test.slow decorator is currently broken. Needs to be fixed in https://github.com/nasa/openmct/issues/5374
test.slow();

View File

@@ -20,7 +20,10 @@
* at runtime from the About dialog for additional information.
*****************************************************************************/
const { createDomainObjectWithDefaults } = require('../../../../appActions');
const {
createDomainObjectWithDefaults,
setTimeConductorBounds
} = require('../../../../appActions');
const { test, expect } = require('../../../../pluginFixtures');
test.describe('Telemetry Table', () => {
@@ -51,18 +54,14 @@ test.describe('Telemetry Table', () => {
await expect(tableWrapper).toHaveClass(/is-paused/);
// Subtract 5 minutes from the current end bound datetime and set it
const endTimeInput = page.locator('input[type="text"].c-input--datetime').nth(1);
await endTimeInput.click();
let endDate = await endTimeInput.inputValue();
// Bring up the time conductor popup
let endDate = await page.locator('[aria-label="End bounds"]').textContent();
endDate = new Date(endDate);
endDate.setUTCMinutes(endDate.getUTCMinutes() - 5);
endDate = endDate.toISOString().replace(/T/, ' ');
await endTimeInput.fill('');
await endTimeInput.fill(endDate);
await page.keyboard.press('Enter');
await setTimeConductorBounds(page, undefined, endDate);
await expect(tableWrapper).not.toHaveClass(/is-paused/);

View File

@@ -25,7 +25,8 @@ const {
setFixedTimeMode,
setRealTimeMode,
setStartOffset,
setEndOffset
setEndOffset,
setTimeConductorBounds
} = require('../../../../appActions');
test.describe('Time conductor operations', () => {
@@ -40,38 +41,36 @@ test.describe('Time conductor operations', () => {
let endDate = 'xxxx-01-01 02:00:00.000Z';
endDate = year + endDate.substring(4);
const startTimeLocator = page.locator('input[type="text"]').first();
const endTimeLocator = page.locator('input[type="text"]').nth(1);
// Click start time
await startTimeLocator.click();
// Click end time
await endTimeLocator.click();
await endTimeLocator.fill(endDate.toString());
await startTimeLocator.fill(startDate.toString());
await setTimeConductorBounds(page, startDate, endDate);
// invalid start date
startDate = year + 1 + startDate.substring(4);
await startTimeLocator.fill(startDate.toString());
await endTimeLocator.click();
await setTimeConductorBounds(page, startDate);
const startDateValidityStatus = await startTimeLocator.evaluate((element) =>
// Bring up the time conductor popup
const timeConductorMode = await page.locator('.c-compact-tc');
await timeConductorMode.click();
const startDateLocator = page.locator('input[type="text"]').first();
const endDateLocator = page.locator('input[type="text"]').nth(2);
await endDateLocator.click();
const startDateValidityStatus = await startDateLocator.evaluate((element) =>
element.checkValidity()
);
expect(startDateValidityStatus).not.toBeTruthy();
// fix to valid start date
startDate = year - 1 + startDate.substring(4);
await startTimeLocator.fill(startDate.toString());
await setTimeConductorBounds(page, startDate);
// invalid end date
endDate = year - 2 + endDate.substring(4);
await endTimeLocator.fill(endDate.toString());
await startTimeLocator.click();
await setTimeConductorBounds(page, undefined, endDate);
const endDateValidityStatus = await endTimeLocator.evaluate((element) =>
await startDateLocator.click();
const endDateValidityStatus = await endDateLocator.evaluate((element) =>
element.checkValidity()
);
expect(endDateValidityStatus).not.toBeTruthy();
@@ -83,11 +82,11 @@ test.describe('Time conductor operations', () => {
test.describe('Time conductor input fields real-time mode', () => {
test('validate input fields in real-time mode', async ({ page }) => {
const startOffset = {
secs: '23'
startSecs: '23'
};
const endOffset = {
secs: '31'
endSecs: '31'
};
// Go to baseURL
@@ -100,15 +99,13 @@ test.describe('Time conductor input fields real-time mode', () => {
await setStartOffset(page, startOffset);
// Verify time was updated on time offset button
await expect(page.locator('data-testid=conductor-start-offset-button')).toContainText(
'00:30:23'
);
await expect(page.locator('.c-compact-tc__setting-value.icon-minus')).toContainText('00:30:23');
// Set end time offset
await setEndOffset(page, endOffset);
// Verify time was updated on preceding time offset button
await expect(page.locator('data-testid=conductor-end-offset-button')).toContainText('00:00:31');
await expect(page.locator('.c-compact-tc__setting-value.icon-plus')).toContainText('00:00:31');
});
/**
@@ -119,12 +116,12 @@ test.describe('Time conductor input fields real-time mode', () => {
page
}) => {
const startOffset = {
mins: '30',
secs: '23'
startMins: '30',
startSecs: '23'
};
const endOffset = {
secs: '01'
endSecs: '01'
};
// Convert offsets to milliseconds
@@ -150,12 +147,10 @@ test.describe('Time conductor input fields real-time mode', () => {
await setRealTimeMode(page);
// Verify updated start time offset persists after mode switch
await expect(page.locator('data-testid=conductor-start-offset-button')).toContainText(
'00:30:23'
);
await expect(page.locator('.c-compact-tc__setting-value.icon-minus')).toContainText('00:30:23');
// Verify updated end time offset persists after mode switch
await expect(page.locator('data-testid=conductor-end-offset-button')).toContainText('00:00:01');
await expect(page.locator('.c-compact-tc__setting-value.icon-plus')).toContainText('00:00:01');
// Verify url parameters persist after mode switch
await page.waitForNavigation({ waitUntil: 'networkidle' });
@@ -203,11 +198,11 @@ test.describe('Time Conductor History', () => {
// with startBound at 2022-01-01 00:00:00.000Z
// and endBound at 2022-01-01 00:00:00.200Z
await page.goto(
'./#/browse/mine?view=grid&tc.mode=fixed&tc.startBound=1640995200000&tc.endBound=1640995200200&tc.timeSystem=utc&hideInspector=true',
{ waitUntil: 'networkidle' }
'./#/browse/mine?view=grid&tc.mode=fixed&tc.startBound=1640995200000&tc.endBound=1640995200200&tc.timeSystem=utc&hideInspector=true'
);
await page.locator("[aria-label='Time Conductor History']").hover({ trial: true });
await page.locator("[aria-label='Time Conductor History']").click();
await page.getByRole('button', { name: 'Time Conductor Settings' }).click();
await page.getByRole('button', { name: 'Time Conductor History' }).hover({ trial: true });
await page.getByRole('button', { name: 'Time Conductor History' }).click();
// Validate history item format
const historyItem = page.locator('text="2022-01-01 00:00:00 + 200ms"');