Welcome to mirror list, hosted at ThFree Co, Russian Federation.

exampleImagery.e2e.spec.js « imagery « plugins « tests « e2e - github.com/nasa/openmct.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
blob: d4f767be87fcd5ee0bdd5152ab09e8a436fc6311 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
/*****************************************************************************
 * 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.
 *****************************************************************************/

/*
This test suite is dedicated to tests which verify the basic operations surrounding imagery,
but only assume that example imagery is present.
*/
/* globals process */

const { test } = require('../../../fixtures.js');
const { expect } = require('@playwright/test');

const backgroundImageSelector = '.c-imagery__main-image__background-image';

//The following block of tests verifies the basic functionality of example imagery and serves as a template for Imagery objects embedded in other objects.
test.describe('Example Imagery Object', () => {

    test.beforeEach(async ({ page }) => {
        //Go to baseURL
        await page.goto('/', { waitUntil: 'networkidle' });

        //Click the Create button
        await page.click('button:has-text("Create")');

        // Click text=Example Imagery
        await page.click('text=Example Imagery');

        // Click text=OK
        await Promise.all([
            page.waitForNavigation({waitUntil: 'networkidle'}),
            page.click('text=OK'),
            //Wait for Save Banner to appear
            page.waitForSelector('.c-message-banner__message')
        ]);
        // Close Banner
        await page.locator('.c-message-banner__close-button').click();

        //Wait until Save Banner is gone
        await page.waitForSelector('.c-message-banner__message', { state: 'detached'});
        await expect(page.locator('.l-browse-bar__object-name')).toContainText('Unnamed Example Imagery');
        await page.locator(backgroundImageSelector).hover({trial: true});
    });

    test('Can use Mouse Wheel to zoom in and out of latest image', async ({ page }) => {
        const deltaYStep = 100; //equivalent to 1x zoom
        await page.locator(backgroundImageSelector).hover({trial: true});
        const originalImageDimensions = await page.locator(backgroundImageSelector).boundingBox();
        // zoom in
        await page.locator(backgroundImageSelector).hover({trial: true});
        await page.mouse.wheel(0, deltaYStep * 2);
        // wait for zoom animation to finish
        await page.locator(backgroundImageSelector).hover({trial: true});
        const imageMouseZoomedIn = await page.locator(backgroundImageSelector).boundingBox();
        // zoom out
        await page.locator(backgroundImageSelector).hover({trial: true});
        await page.mouse.wheel(0, -deltaYStep);
        // wait for zoom animation to finish
        await page.locator(backgroundImageSelector).hover({trial: true});
        const imageMouseZoomedOut = await page.locator(backgroundImageSelector).boundingBox();

        expect(imageMouseZoomedIn.height).toBeGreaterThan(originalImageDimensions.height);
        expect(imageMouseZoomedIn.width).toBeGreaterThan(originalImageDimensions.width);
        expect(imageMouseZoomedOut.height).toBeLessThan(imageMouseZoomedIn.height);
        expect(imageMouseZoomedOut.width).toBeLessThan(imageMouseZoomedIn.width);

    });

    test('Can use alt+drag to move around image once zoomed in', async ({ page }) => {
        const deltaYStep = 100; //equivalent to 1x zoom
        const panHotkey = process.platform === 'linux' ? ['Control', 'Alt'] : ['Alt'];

        await page.locator(backgroundImageSelector).hover({trial: true});

        // zoom in
        await page.mouse.wheel(0, deltaYStep * 2);
        await page.locator(backgroundImageSelector).hover({trial: true});
        const zoomedBoundingBox = await page.locator(backgroundImageSelector).boundingBox();
        const imageCenterX = zoomedBoundingBox.x + zoomedBoundingBox.width / 2;
        const imageCenterY = zoomedBoundingBox.y + zoomedBoundingBox.height / 2;
        // move to the right

        // center the mouse pointer
        await page.mouse.move(imageCenterX, imageCenterY);

        //Get Diagnostic info about process environment
        console.log('process.platform is ' + process.platform);
        const getUA = await page.evaluate(() => navigator.userAgent);
        console.log('navigator.userAgent ' + getUA);
        // Pan Imagery Hints
        const expectedAltText = process.platform === 'linux' ? 'Ctrl+Alt drag to pan' : 'Alt drag to pan';
        const imageryHintsText = await page.locator('.c-imagery__hints').innerText();
        expect(expectedAltText).toEqual(imageryHintsText);

        // pan right
        await Promise.all(panHotkey.map(x => page.keyboard.down(x)));
        await page.mouse.down();
        await page.mouse.move(imageCenterX - 200, imageCenterY, 10);
        await page.mouse.up();
        await Promise.all(panHotkey.map(x => page.keyboard.up(x)));
        const afterRightPanBoundingBox = await page.locator(backgroundImageSelector).boundingBox();
        expect(zoomedBoundingBox.x).toBeGreaterThan(afterRightPanBoundingBox.x);

        // pan left
        await Promise.all(panHotkey.map(x => page.keyboard.down(x)));
        await page.mouse.down();
        await page.mouse.move(imageCenterX, imageCenterY, 10);
        await page.mouse.up();
        await Promise.all(panHotkey.map(x => page.keyboard.up(x)));
        const afterLeftPanBoundingBox = await page.locator(backgroundImageSelector).boundingBox();
        expect(afterRightPanBoundingBox.x).toBeLessThan(afterLeftPanBoundingBox.x);

        // pan up
        await page.mouse.move(imageCenterX, imageCenterY);
        await Promise.all(panHotkey.map(x => page.keyboard.down(x)));
        await page.mouse.down();
        await page.mouse.move(imageCenterX, imageCenterY + 200, 10);
        await page.mouse.up();
        await Promise.all(panHotkey.map(x => page.keyboard.up(x)));
        const afterUpPanBoundingBox = await page.locator(backgroundImageSelector).boundingBox();
        expect(afterUpPanBoundingBox.y).toBeGreaterThan(afterLeftPanBoundingBox.y);

        // pan down
        await Promise.all(panHotkey.map(x => page.keyboard.down(x)));
        await page.mouse.down();
        await page.mouse.move(imageCenterX, imageCenterY - 200, 10);
        await page.mouse.up();
        await Promise.all(panHotkey.map(x => page.keyboard.up(x)));
        const afterDownPanBoundingBox = await page.locator(backgroundImageSelector).boundingBox();
        expect(afterDownPanBoundingBox.y).toBeLessThan(afterUpPanBoundingBox.y);

    });

    test('Can use + - buttons to zoom on the image', async ({ page }) => {
        await page.locator(backgroundImageSelector).hover({trial: true});
        const zoomInBtn = page.locator('.t-btn-zoom-in').nth(0);
        const zoomOutBtn = page.locator('.t-btn-zoom-out').nth(0);
        const initialBoundingBox = await page.locator(backgroundImageSelector).boundingBox();

        await zoomInBtn.click();
        await zoomInBtn.click();
        // wait for zoom animation to finish
        await page.locator(backgroundImageSelector).hover({trial: true});
        const zoomedInBoundingBox = await page.locator(backgroundImageSelector).boundingBox();
        expect(zoomedInBoundingBox.height).toBeGreaterThan(initialBoundingBox.height);
        expect(zoomedInBoundingBox.width).toBeGreaterThan(initialBoundingBox.width);

        await zoomOutBtn.click();
        // wait for zoom animation to finish
        await page.locator(backgroundImageSelector).hover({trial: true});
        const zoomedOutBoundingBox = await page.locator(backgroundImageSelector).boundingBox();
        expect(zoomedOutBoundingBox.height).toBeLessThan(zoomedInBoundingBox.height);
        expect(zoomedOutBoundingBox.width).toBeLessThan(zoomedInBoundingBox.width);

    });

    test('Can use the reset button to reset the image', async ({ page }, testInfo) => {
        test.slow(testInfo.project === 'chrome-beta', "This test is slow in chrome-beta");
        // wait for zoom animation to finish
        await page.locator(backgroundImageSelector).hover({trial: true});

        const zoomInBtn = page.locator('.t-btn-zoom-in').nth(0);
        const zoomResetBtn = page.locator('.t-btn-zoom-reset').nth(0);
        const initialBoundingBox = await page.locator(backgroundImageSelector).boundingBox();

        await zoomInBtn.click();
        // wait for zoom animation to finish
        await page.locator(backgroundImageSelector).hover({trial: true});
        await zoomInBtn.click();
        // wait for zoom animation to finish
        await page.locator(backgroundImageSelector).hover({trial: true});

        const zoomedInBoundingBox = await page.locator(backgroundImageSelector).boundingBox();
        expect.soft(zoomedInBoundingBox.height).toBeGreaterThan(initialBoundingBox.height);
        expect.soft(zoomedInBoundingBox.width).toBeGreaterThan(initialBoundingBox.width);

        // wait for zoom animation to finish
        // FIXME: The zoom is flakey, sometimes not returning to original dimensions
        // https://github.com/nasa/openmct/issues/5491
        await expect.poll(async () => {
            await zoomResetBtn.click();
            const boundingBox = await page.locator(backgroundImageSelector).boundingBox();

            return boundingBox;
        }, {
            timeout: 10 * 1000
        }).toEqual(initialBoundingBox);
    });

    test('Using the zoom features does not pause telemetry', async ({ page }) => {
        const pausePlayButton = page.locator('.c-button.pause-play');
        // wait for zoom animation to finish
        await page.locator(backgroundImageSelector).hover({trial: true});

        // open the time conductor drop down
        await page.locator('button:has-text("Fixed Timespan")').click();
        // Click local clock
        await page.locator('[data-testid="conductor-modeOption-realtime"]').click();

        await expect.soft(pausePlayButton).not.toHaveClass(/is-paused/);
        const zoomInBtn = page.locator('.t-btn-zoom-in').nth(0);
        await zoomInBtn.click();
        // wait for zoom animation to finish
        await page.locator(backgroundImageSelector).hover({trial: true});

        return expect(pausePlayButton).not.toHaveClass(/is-paused/);
    });

});

// The following test case will cover these scenarios
// ('Can use Mouse Wheel to zoom in and out of previous image');
// ('Can use alt+drag to move around image once zoomed in');
// ('Clicking on the left arrow should pause the imagery and go to previous image');
// ('If the imagery view is in pause mode, it should not be updated when new images come in');
// ('If the imagery view is not in pause mode, it should be updated when new images come in');
test('Example Imagery in Display layout', async ({ page, browserName }) => {
    test.fixme(browserName === 'firefox', 'This test needs to be updated to work with firefox');
    test.info().annotations.push({
        type: 'issue',
        description: 'https://github.com/nasa/openmct/issues/5265'
    });

    // Go to baseURL
    await page.goto('/', { waitUntil: 'networkidle' });

    // Click the Create button
    await page.click('button:has-text("Create")');

    // Click text=Example Imagery
    await page.click('text=Example Imagery');

    // Clear and set Image load delay to minimum value
    // FIXME: Update the value to 5000 ms when this bug is fixed.
    // See: https://github.com/nasa/openmct/issues/5265
    await page.locator('input[type="number"]').fill('');
    await page.locator('input[type="number"]').fill('0');

    // Click text=OK
    await Promise.all([
        page.waitForNavigation({waitUntil: 'networkidle'}),
        page.click('text=OK'),
        //Wait for Save Banner to appear
        page.waitForSelector('.c-message-banner__message')
    ]);

    // Wait until Save Banner is gone
    await page.waitForSelector('.c-message-banner__message', { state: 'detached'});
    await expect(page.locator('.l-browse-bar__object-name')).toContainText('Unnamed Example Imagery');
    await page.locator(backgroundImageSelector).hover({trial: true});

    // Click previous image button
    const previousImageButton = page.locator('.c-nav--prev');
    await previousImageButton.click();

    // Verify previous image
    const selectedImage = page.locator('.selected');
    await expect(selectedImage).toBeVisible();

    // Zoom in
    const originalImageDimensions = await page.locator(backgroundImageSelector).boundingBox();
    await page.locator(backgroundImageSelector).hover({trial: true});
    const deltaYStep = 100; // equivalent to 1x zoom
    await page.mouse.wheel(0, deltaYStep * 2);
    const zoomedBoundingBox = await page.locator(backgroundImageSelector).boundingBox();
    const imageCenterX = zoomedBoundingBox.x + zoomedBoundingBox.width / 2;
    const imageCenterY = zoomedBoundingBox.y + zoomedBoundingBox.height / 2;

    // Wait for zoom animation to finish
    await page.locator(backgroundImageSelector).hover({trial: true});
    const imageMouseZoomedIn = await page.locator(backgroundImageSelector).boundingBox();
    expect(imageMouseZoomedIn.height).toBeGreaterThan(originalImageDimensions.height);
    expect(imageMouseZoomedIn.width).toBeGreaterThan(originalImageDimensions.width);

    // Center the mouse pointer
    await page.mouse.move(imageCenterX, imageCenterY);

    // Pan Imagery Hints
    const expectedAltText = process.platform === 'linux' ? 'Ctrl+Alt drag to pan' : 'Alt drag to pan';
    const imageryHintsText = await page.locator('.c-imagery__hints').innerText();
    expect(expectedAltText).toEqual(imageryHintsText);

    // Click next image button
    const nextImageButton = page.locator('.c-nav--next');
    await nextImageButton.click();

    // Click time conductor mode button
    await page.locator('.c-mode-button').click();

    // Select local clock mode
    await page.locator('[data-testid=conductor-modeOption-realtime]').click();

    // Zoom in on next image
    await page.locator(backgroundImageSelector).hover({trial: true});
    await page.mouse.wheel(0, deltaYStep * 2);

    // Wait for zoom animation to finish
    await page.locator(backgroundImageSelector).hover({trial: true});
    const imageNextMouseZoomedIn = await page.locator(backgroundImageSelector).boundingBox();
    expect(imageNextMouseZoomedIn.height).toBeGreaterThan(originalImageDimensions.height);
    expect(imageNextMouseZoomedIn.width).toBeGreaterThan(originalImageDimensions.width);

    // Click previous image button
    await previousImageButton.click();

    // Verify previous image
    await expect(selectedImage).toBeVisible();

    const imageCount = await page.locator('.c-imagery__thumb').count();
    await expect.poll(async () => {
        const newImageCount = await page.locator('.c-imagery__thumb').count();

        return newImageCount;
    }, {
        message: "verify that old images are discarded",
        timeout: 6 * 1000
    }).toBe(imageCount);

    // Verify selected image is still displayed
    await expect(selectedImage).toBeVisible();
});

test.describe('Example imagery thumbnails resize in display layouts', () => {
    test('Resizing the layout changes thumbnail visibility and size', async ({ page }) => {
        await page.goto('/', { waitUntil: 'networkidle' });

        const thumbsWrapperLocator = page.locator('.c-imagery__thumbs-wrapper');
        // Click button:has-text("Create")
        await page.locator('button:has-text("Create")').click();

        // Click li:has-text("Display Layout")
        await page.locator('li:has-text("Display Layout")').click();
        const displayLayoutTitleField = page.locator('text=Properties Title Notes Horizontal grid (px) Vertical grid (px) Horizontal size ( >> input[type="text"]');
        await displayLayoutTitleField.click();

        await displayLayoutTitleField.fill('Thumbnail Display Layout');

        // Click text=OK
        await Promise.all([
            page.waitForNavigation(),
            page.locator('text=OK').click()
        ]);

        // Click text=Snapshot Save and Finish Editing Save and Continue Editing >> button >> nth=1
        await page.locator('text=Snapshot Save and Finish Editing Save and Continue Editing >> button').nth(1).click();

        // Click text=Save and Finish Editing
        await page.locator('text=Save and Finish Editing').click();

        // Click button:has-text("Create")
        await page.locator('button:has-text("Create")').click();

        // Click li:has-text("Example Imagery")
        await page.locator('li:has-text("Example Imagery")').click();

        const imageryTitleField = page.locator('text=Properties Title Notes Images url list (comma separated) Image load delay (milli >> input[type="text"]');
        // Click text=Properties Title Notes Images url list (comma separated) Image load delay (milli >> input[type="text"]
        await imageryTitleField.click();

        // Fill text=Properties Title Notes Images url list (comma separated) Image load delay (milli >> input[type="text"]
        await imageryTitleField.fill('Thumbnail Example Imagery');

        // Click text=OK
        await Promise.all([
            page.waitForNavigation(),
            page.locator('text=OK').click()
        ]);

        // Click text=Thumbnail Example Imagery Imagery Layout Snapshot >> button >> nth=0
        await Promise.all([
            page.waitForNavigation(),
            page.locator('text=Thumbnail Example Imagery Imagery Layout Snapshot >> button').first().click()
        ]);

        // Edit mode
        await page.locator('text=Thumbnail Display Layout Snapshot >> button').nth(3).click();

        // Click on example imagery to expose toolbar
        await page.locator('text=Thumbnail Example Imagery Snapshot Large View').click();

        // expect thumbnails not be visible when first added
        expect.soft(thumbsWrapperLocator.isHidden()).toBeTruthy();

        // Resize the example imagery vertically to change the thumbnail visibility
        /*
        The following arbitrary values are added to observe the separate visual
        conditions of the thumbnails (hidden, small thumbnails, regular thumbnails).
        Specifically, height is set to 50px for small thumbs and 100px for regular
        */
        // Click #mct-input-id-103
        await page.locator('#mct-input-id-103').click();

        // Fill #mct-input-id-103
        await page.locator('#mct-input-id-103').fill('50');

        expect(thumbsWrapperLocator.isVisible()).toBeTruthy();
        await expect(thumbsWrapperLocator).toHaveClass(/is-small-thumbs/);

        // Resize the example imagery vertically to change the thumbnail visibility
        // Click #mct-input-id-103
        await page.locator('#mct-input-id-103').click();

        // Fill #mct-input-id-103
        await page.locator('#mct-input-id-103').fill('100');

        expect(thumbsWrapperLocator.isVisible()).toBeTruthy();
        await expect(thumbsWrapperLocator).not.toHaveClass(/is-small-thumbs/);
    });
});

test.describe('Example Imagery in Flexible layout', () => {
    test('Example Imagery in Flexible layout', async ({ page, browserName }) => {
        test.fixme(browserName === 'firefox', 'This test needs to be updated to work with firefox');
        test.info().annotations.push({
            type: 'issue',
            description: 'https://github.com/nasa/openmct/issues/5326'
        });

        // Go to baseURL
        await page.goto('/', { waitUntil: 'networkidle' });

        // Click the Create button
        await page.click('button:has-text("Create")');

        // Click text=Example Imagery
        await page.click('text=Example Imagery');

        // Clear and set Image load delay (milliseconds)
        await page.click('input[type="number"]', {clickCount: 3});
        await page.type('input[type="number"]', "20");

        // Click text=OK
        await Promise.all([
            page.waitForNavigation({waitUntil: 'networkidle'}),
            page.click('text=OK'),
            //Wait for Save Banner to appear
            page.waitForSelector('.c-message-banner__message')
        ]);
        // Wait until Save Banner is gone
        await page.waitForSelector('.c-message-banner__message', { state: 'detached'});
        await expect(page.locator('.l-browse-bar__object-name')).toContainText('Unnamed Example Imagery');
        await page.locator(backgroundImageSelector).hover({trial: true});

        // Click the Create button
        await page.click('button:has-text("Create")');

        // Click text=Flexible Layout
        await page.click('text=Flexible Layout');

        // Assert Flexable layout
        await expect(page.locator('.js-form-title')).toHaveText('Create a New Flexible Layout');

        await page.locator('form[name="mctForm"] >> text=My Items').click();

        // Click My Items
        await Promise.all([
            page.locator('text=OK').click(),
            page.waitForNavigation({waitUntil: 'networkidle'})
        ]);

        // Click My Items
        await page.locator('.c-disclosure-triangle').click();

        // Right click example imagery
        await page.click(('text=Unnamed Example Imagery'), { button: 'right' });

        // Click move
        await page.locator('.icon-move').click();

        // Click triangle to open sub menu
        await page.locator('.c-form__section .c-disclosure-triangle').click();

        // Click Flexable Layout
        await page.click('.c-overlay__outer >> text=Unnamed Flexible Layout');

        // Click text=OK
        await page.locator('text=OK').click();

        // Save template
        await saveTemplate(page);

        // Zoom in
        await mouseZoomIn(page);

        // Center the mouse pointer
        const zoomedBoundingBox = await await page.locator(backgroundImageSelector).boundingBox();
        const imageCenterX = zoomedBoundingBox.x + zoomedBoundingBox.width / 2;
        const imageCenterY = zoomedBoundingBox.y + zoomedBoundingBox.height / 2;
        await page.mouse.move(imageCenterX, imageCenterY);

        // Pan zoom
        await panZoomAndAssertImageProperties(page);

        // Click previous image button
        const previousImageButton = page.locator('.c-nav--prev');
        await previousImageButton.click();

        // Verify previous image
        const selectedImage = page.locator('.selected');
        await expect(selectedImage).toBeVisible();

        // Click time conductor mode button
        await page.locator('.c-mode-button').click();

        // Select local clock mode
        await page.locator('[data-testid=conductor-modeOption-realtime]').click();

        // Zoom in on next image
        await mouseZoomIn(page);

        // Click previous image button
        await previousImageButton.click();

        // Verify previous image
        await expect(selectedImage).toBeVisible();

        const imageCount = await page.locator('.c-imagery__thumb').count();
        await expect.poll(async () => {
            const newImageCount = await page.locator('.c-imagery__thumb').count();

            return newImageCount;
        }, {
            message: "verify that old images are discarded",
            timeout: 6 * 1000
        }).toBe(imageCount);

        // Verify selected image is still displayed
        await expect(selectedImage).toBeVisible();

        // Unpause imagery
        await page.locator('.pause-play').click();

        //Get background-image url from background-image css prop
        await assertBackgroundImageUrlFromBackgroundCss(page);

        // Open the image filter menu
        await page.locator('[role=toolbar] button[title="Brightness and contrast"]').click();

        // Drag the brightness and contrast sliders around and assert filter values
        await dragBrightnessSliderAndAssertFilterValues(page);
        await dragContrastSliderAndAssertFilterValues(page);
    });
});

test.describe('Example Imagery in Tabs view', () => {
    test.fixme('Can use Mouse Wheel to zoom in and out of previous image');
    test.fixme('Can use alt+drag to move around image once zoomed in');
    test.fixme('Can zoom into the latest image and the real-time/fixed-time imagery will pause');
    test.fixme('Can zoom into a previous image from thumbstrip in real-time or fixed-time');
    test.fixme('Clicking on the left arrow should pause the imagery and go to previous image');
    test.fixme('If the imagery view is in pause mode, it should not be updated when new images come in');
    test.fixme('If the imagery view is not in pause mode, it should be updated when new images come in');
});

/**
 * @param {import('@playwright/test').Page} page
 */
async function saveTemplate(page) {
    await page.locator('.c-button--menu.c-button--major.icon-save').click();
    await page.locator('text=Save and Finish Editing').click();
}

/**
 * Drag the brightness slider to max, min, and midpoint and assert the filter values
 * @param {import('@playwright/test').Page} page
 */
async function dragBrightnessSliderAndAssertFilterValues(page) {
    const brightnessSlider = 'div.c-image-controls__slider-wrapper.icon-brightness > input';
    const brightnessBoundingBox = await page.locator(brightnessSlider).boundingBox();
    const brightnessMidX = brightnessBoundingBox.x + brightnessBoundingBox.width / 2;
    const brightnessMidY = brightnessBoundingBox.y + brightnessBoundingBox.height / 2;

    await page.locator(brightnessSlider).hover({trial: true});
    await page.mouse.down();
    await page.mouse.move(brightnessBoundingBox.x + brightnessBoundingBox.width, brightnessMidY);
    await assertBackgroundImageBrightness(page, '500');
    await page.mouse.move(brightnessBoundingBox.x, brightnessMidY);
    await assertBackgroundImageBrightness(page, '0');
    await page.mouse.move(brightnessMidX, brightnessMidY);
    await assertBackgroundImageBrightness(page, '250');
    await page.mouse.up();
}

/**
 * Drag the contrast slider to max, min, and midpoint and assert the filter values
 * @param {import('@playwright/test').Page} page
 */
async function dragContrastSliderAndAssertFilterValues(page) {
    const contrastSlider = 'div.c-image-controls__slider-wrapper.icon-contrast > input';
    const contrastBoundingBox = await page.locator(contrastSlider).boundingBox();
    const contrastMidX = contrastBoundingBox.x + contrastBoundingBox.width / 2;
    const contrastMidY = contrastBoundingBox.y + contrastBoundingBox.height / 2;

    await page.locator(contrastSlider).hover({trial: true});
    await page.mouse.down();
    await page.mouse.move(contrastBoundingBox.x + contrastBoundingBox.width, contrastMidY);
    await assertBackgroundImageContrast(page, '500');
    await page.mouse.move(contrastBoundingBox.x, contrastMidY);
    await assertBackgroundImageContrast(page, '0');
    await page.mouse.move(contrastMidX, contrastMidY);
    await assertBackgroundImageContrast(page, '250');
    await page.mouse.up();
}

/**
 * Gets the filter:brightness value of the current background-image and
 * asserts against an expected value
 * @param {import('@playwright/test').Page} page
 * @param {String} expected The expected brightness value
 */
async function assertBackgroundImageBrightness(page, expected) {
    const backgroundImage = page.locator('.c-imagery__main-image__background-image');

    // Get the brightness filter value (i.e: filter: brightness(500%) => "500")
    const actual = await backgroundImage.evaluate((el) => {
        return el.style.filter.match(/brightness\((\d{1,3})%\)/)[1];
    });
    expect(actual).toBe(expected);
}

/**
 * Gets the filter:contrast value of the current background-image and
 * asserts against an expected value
 * @param {import('@playwright/test').Page} page
 * @param {String} expected The expected contrast value
 */
async function assertBackgroundImageContrast(page, expected) {
    const backgroundImage = page.locator('.c-imagery__main-image__background-image');

    // Get the contrast filter value (i.e: filter: contrast(500%) => "500")
    const actual = await backgroundImage.evaluate((el) => {
        return el.style.filter.match(/contrast\((\d{1,3})%\)/)[1];
    });
    expect(actual).toBe(expected);
}

/**
 * @param {import('@playwright/test').Page} page
 */
async function assertBackgroundImageUrlFromBackgroundCss(page) {
    const backgroundImage = page.locator('.c-imagery__main-image__background-image');
    let backgroundImageUrl = await backgroundImage.evaluate((el) => {
        return window.getComputedStyle(el).getPropertyValue('background-image').match(/url\(([^)]+)\)/)[1];
    });
    let backgroundImageUrl1 = backgroundImageUrl.slice(1, -1); //forgive me, padre
    console.log('backgroundImageUrl1 ' + backgroundImageUrl1);

    let backgroundImageUrl2;
    await expect.poll(async () => {
        // Verify next image has updated
        let backgroundImageUrlNext = await backgroundImage.evaluate((el) => {
            return window.getComputedStyle(el).getPropertyValue('background-image').match(/url\(([^)]+)\)/)[1];
        });
        backgroundImageUrl2 = backgroundImageUrlNext.slice(1, -1); //forgive me, padre

        return backgroundImageUrl2;
    }, {
        message: "verify next image has updated",
        timeout: 6 * 1000
    }).not.toBe(backgroundImageUrl1);
    console.log('backgroundImageUrl2 ' + backgroundImageUrl2);
}

/**
 * @param {import('@playwright/test').Page} page
 */
async function panZoomAndAssertImageProperties(page) {
    const panHotkey = process.platform === 'linux' ? ['Control', 'Alt'] : ['Alt'];
    const expectedAltText = process.platform === 'linux' ? 'Ctrl+Alt drag to pan' : 'Alt drag to pan';
    const imageryHintsText = await page.locator('.c-imagery__hints').innerText();
    expect(expectedAltText).toEqual(imageryHintsText);
    const zoomedBoundingBox = await page.locator(backgroundImageSelector).boundingBox();
    const imageCenterX = zoomedBoundingBox.x + zoomedBoundingBox.width / 2;
    const imageCenterY = zoomedBoundingBox.y + zoomedBoundingBox.height / 2;

    // Pan right
    await Promise.all(panHotkey.map(x => page.keyboard.down(x)));
    await page.mouse.down();
    await page.mouse.move(imageCenterX - 200, imageCenterY, 10);
    await page.mouse.up();
    await Promise.all(panHotkey.map(x => page.keyboard.up(x)));
    const afterRightPanBoundingBox = await page.locator(backgroundImageSelector).boundingBox();
    expect(zoomedBoundingBox.x).toBeGreaterThan(afterRightPanBoundingBox.x);

    // Pan left
    await Promise.all(panHotkey.map(x => page.keyboard.down(x)));
    await page.mouse.down();
    await page.mouse.move(imageCenterX, imageCenterY, 10);
    await page.mouse.up();
    await Promise.all(panHotkey.map(x => page.keyboard.up(x)));
    const afterLeftPanBoundingBox = await page.locator(backgroundImageSelector).boundingBox();
    expect(afterRightPanBoundingBox.x).toBeLessThan(afterLeftPanBoundingBox.x);

    // Pan up
    await page.mouse.move(imageCenterX, imageCenterY);
    await Promise.all(panHotkey.map(x => page.keyboard.down(x)));
    await page.mouse.down();
    await page.mouse.move(imageCenterX, imageCenterY + 200, 10);
    await page.mouse.up();
    await Promise.all(panHotkey.map(x => page.keyboard.up(x)));
    const afterUpPanBoundingBox = await page.locator(backgroundImageSelector).boundingBox();
    expect(afterUpPanBoundingBox.y).toBeGreaterThanOrEqual(afterLeftPanBoundingBox.y);

    // Pan down
    await Promise.all(panHotkey.map(x => page.keyboard.down(x)));
    await page.mouse.down();
    await page.mouse.move(imageCenterX, imageCenterY - 200, 10);
    await page.mouse.up();
    await Promise.all(panHotkey.map(x => page.keyboard.up(x)));
    const afterDownPanBoundingBox = await page.locator(backgroundImageSelector).boundingBox();
    expect(afterDownPanBoundingBox.y).toBeLessThanOrEqual(afterUpPanBoundingBox.y);
}

/**
 * @param {import('@playwright/test').Page} page
*/
async function mouseZoomIn(page) {
    // Zoom in
    const originalImageDimensions = await page.locator(backgroundImageSelector).boundingBox();
    await page.locator(backgroundImageSelector).hover({trial: true});
    const deltaYStep = 100; // equivalent to 1x zoom
    await page.mouse.wheel(0, deltaYStep * 2);
    const zoomedBoundingBox = await page.locator(backgroundImageSelector).boundingBox();
    const imageCenterX = zoomedBoundingBox.x + zoomedBoundingBox.width / 2;
    const imageCenterY = zoomedBoundingBox.y + zoomedBoundingBox.height / 2;

    // center the mouse pointer
    await page.mouse.move(imageCenterX, imageCenterY);

    // Wait for zoom animation to finish
    await page.locator(backgroundImageSelector).hover({trial: true});
    const imageMouseZoomedIn = await page.locator(backgroundImageSelector).boundingBox();
    expect(imageMouseZoomedIn.height).toBeGreaterThan(originalImageDimensions.height);
    expect(imageMouseZoomedIn.width).toBeGreaterThan(originalImageDimensions.width);
}