diff --git a/.circleci/config.yml b/.circleci/config.yml index 8863b9e057..6814113ac1 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -2,7 +2,7 @@ version: 2.1 executors: pw-focal-development: docker: - - image: mcr.microsoft.com/playwright:v1.21.1-focal + - image: mcr.microsoft.com/playwright:v1.23.0-focal environment: NODE_ENV: development # Needed to ensure 'dist' folder created and devDependencies installed parameters: @@ -12,7 +12,7 @@ parameters: type: boolean commands: build_and_install: - description: "All steps used to build and install. Will not work on node10" + description: "All steps used to build and install. Will use cache if found" parameters: node-version: type: string @@ -58,10 +58,14 @@ commands: ls -latR >> /tmp/artifacts/dir.txt - store_artifacts: path: /tmp/artifacts/ - upload_code_covio: - description: "Command to upload code coverage reports to codecov.io" - steps: - - run: curl -Os https://uploader.codecov.io/latest/linux/codecov;chmod +x codecov;./codecov + generate_e2e_code_cov_report: + description: "Generate e2e code coverage artifacts and publish to codecov.io. Needed to that we can ignore the exit code status of the npm run test" + parameters: + suite: + type: string + steps: + - run: npm run cov:e2e:report + - run: npm run cov:e2e:<>:publish orbs: node: circleci/node@4.9.0 browser-tools: circleci/browser-tools@1.3.0 @@ -114,12 +118,13 @@ jobs: - browser-tools/install-chrome: replace-existing: false - run: npm run test -- --browsers=<> + - run: npm run cov:unit:publish - save_cache_cmd: node-version: <> - store_test_results: path: dist/reports/tests/ - store_artifacts: - path: dist/reports/ + path: coverage - generate_and_store_version_and_filesystem_artifacts e2e-test: parameters: @@ -132,11 +137,22 @@ jobs: steps: - build_and_install: node-version: <> + - when: #Only install chrome-beta when running the full suite to save $$$ + condition: + equal: [ "full", <> ] + steps: + - run: npx playwright install chrome-beta - run: SHARD="$((${CIRCLE_NODE_INDEX}+1))"; npm run test:e2e:<> -- --shard=${SHARD}/${CIRCLE_NODE_TOTAL} + - generate_e2e_code_cov_report: + suite: <> - store_test_results: path: test-results/results.xml - store_artifacts: path: test-results + - store_artifacts: + path: coverage + - store_artifacts: + path: html-test-results - generate_and_store_version_and_filesystem_artifacts perf-test: parameters: @@ -151,19 +167,19 @@ jobs: path: test-results/results.xml - store_artifacts: path: test-results - - generate_and_store_version_and_filesystem_artifacts + - store_artifacts: + path: html-test-results + - generate_and_store_version_and_filesystem_artifacts workflows: overall-circleci-commit-status: #These jobs run on every commit jobs: - lint: - name: node16-lint - node-version: lts/gallium - - unit-test: - name: node14-chrome + name: node14-lint node-version: lts/fermium + - unit-test: + name: node16-chrome + node-version: lts/gallium browser: ChromeHeadless - post-steps: - - upload_code_covio - unit-test: name: node18-chrome node-version: "18" diff --git a/.gitignore b/.gitignore index 7c608343c6..11481c4c40 100644 --- a/.gitignore +++ b/.gitignore @@ -15,8 +15,6 @@ *.idea *.iml -# External dependencies - # Build output target dist @@ -24,30 +22,24 @@ dist # Mac OS X Finder .DS_Store -# Closed source libraries -closed-lib - # Node, Bower dependencies node_modules bower_components -# Protractor logs -protractor/logs - # npm-debug log npm-debug.log # karma reports report.*.json -# Lighthouse reports -.lighthouseci - # e2e test artifacts test-results -allure-results +html-test-results -package-lock.json - -#codecov artifacts +# codecov artifacts +.nyc_output +coverage codecov + +# :( +package-lock.json diff --git a/app.js b/app.js index b1ddaada9f..a1a30ef839 100644 --- a/app.js +++ b/app.js @@ -49,7 +49,7 @@ class WatchRunPlugin { } const webpack = require('webpack'); -const webpackConfig = require('./webpack.dev.js'); +const webpackConfig = process.env.CI ? require('./webpack.coverage.js') : require('./webpack.dev.js'); webpackConfig.plugins.push(new webpack.HotModuleReplacementPlugin()); webpackConfig.plugins.push(new WatchRunPlugin()); diff --git a/codecov.yml b/codecov.yml index 30b299937a..d69c35bbdb 100644 --- a/codecov.yml +++ b/codecov.yml @@ -13,17 +13,16 @@ coverage: round: down range: "66...100" -ignore: - -parsers: - gcov: - branch_detection: - conditional: true - loop: true - method: false - macro: false +flags: + unit: + carryforward: true + e2e-ci: + carryforward: true + e2e-full: + carryforward: true comment: layout: "reach,diff,flags,files,footer" behavior: default require_changes: false + show_carryforward_flags: true \ No newline at end of file diff --git a/e2e/fixtures.js b/e2e/fixtures.js index d602857d1f..ce46de5bbf 100644 --- a/e2e/fixtures.js +++ b/e2e/fixtures.js @@ -1,8 +1,13 @@ -/* eslint-disable no-undef */ +/* This file extends the base functionality of the playwright test framework to enable + * code coverage instrumentation, console log error detection and working with a 3rd + * party Chrome-as-a-service extension called Browserless. + */ -// This file extends the base functionality of the playwright test framework const base = require('@playwright/test'); const { expect } = require('@playwright/test'); +const fs = require('fs'); +const path = require('path'); +const { v4: uuid } = require('uuid'); /** * Takes a `ConsoleMessage` and returns a formatted string @@ -16,7 +21,30 @@ function consoleMessageToString(msg) { at (${url} ${lineNumber}:${columnNumber})`; } +//The following is based on https://github.com/mxschmitt/playwright-test-coverage +// eslint-disable-next-line no-undef +const istanbulCLIOutput = path.join(process.cwd(), '.nyc_output'); + +// eslint-disable-next-line no-undef exports.test = base.test.extend({ + //The following is based on https://github.com/mxschmitt/playwright-test-coverage + context: async ({ context }, use) => { + await context.addInitScript(() => + window.addEventListener('beforeunload', () => + (window).collectIstanbulCoverage(JSON.stringify((window).__coverage__)) + ) + ); + await fs.promises.mkdir(istanbulCLIOutput, { recursive: true }); + await context.exposeFunction('collectIstanbulCoverage', (coverageJSON) => { + if (coverageJSON) { + fs.writeFileSync(path.join(istanbulCLIOutput, `playwright_coverage_${uuid()}.json`), coverageJSON); + } + }); + await use(context); + for (const page of context.pages()) { + await page.evaluate(() => (window).collectIstanbulCoverage(JSON.stringify((window).__coverage__))); + } + }, page: async ({ baseURL, page }, use) => { const messages = []; page.on('console', (msg) => messages.push(msg)); diff --git a/e2e/playwright-ci.config.js b/e2e/playwright-ci.config.js index 0e8f74e8cd..4364d11bd4 100644 --- a/e2e/playwright-ci.config.js +++ b/e2e/playwright-ci.config.js @@ -7,13 +7,13 @@ const { devices } = require('@playwright/test'); /** @type {import('@playwright/test').PlaywrightTestConfig} */ const config = { - retries: 1, + retries: 3, //Retries 3 times for a total of 4. When running sharded and with maxFailures = 5, this should ensure that flake is managed without failing the full suite testDir: 'tests', testIgnore: '**/*.perf.spec.js', //Ignore performance tests and define in playwright-perfromance.config.js timeout: 60 * 1000, webServer: { command: 'npm run start', - port: 8080, + url: 'http://localhost:8080/#', timeout: 200 * 1000, reuseExistingServer: !process.env.CI }, @@ -36,6 +36,7 @@ const config = { }, { name: 'MMOC', + testMatch: '**/*.e2e.spec.js', // only run e2e tests grepInvert: /@snapshot/, use: { browserName: 'chromium', @@ -44,20 +45,30 @@ const config = { height: 1440 } } - } - /*{ - name: 'ipad', + }, + { + name: 'firefox', + testMatch: '**/*.e2e.spec.js', // only run e2e tests + grepInvert: /@snapshot/, use: { - browserName: 'webkit', - ...devices['iPad (gen 7) landscape'] // Complete List https://github.com/microsoft/playwright/blob/main/packages/playwright-core/src/server/deviceDescriptorsSource.json + browserName: 'firefox' } - }*/ + }, + { + name: 'chrome-beta', //Only Chrome Beta is available on ubuntu -- not chrome canary + testMatch: '**/*.e2e.spec.js', // only run e2e tests + grepInvert: /@snapshot/, + use: { + browserName: 'chromium', + channel: 'chrome-beta' + } + } ], reporter: [ ['list'], ['html', { open: 'never', - outputFolder: '../test-results/html/' + outputFolder: '../html-test-results' //Must be in different location due to https://github.com/microsoft/playwright/issues/12840 }], ['junit', { outputFile: 'test-results/results.xml' }], ['github'] diff --git a/e2e/playwright-local.config.js b/e2e/playwright-local.config.js index 124b842643..54f59b303e 100644 --- a/e2e/playwright-local.config.js +++ b/e2e/playwright-local.config.js @@ -13,7 +13,7 @@ const config = { timeout: 30 * 1000, webServer: { command: 'npm run start', - port: 8080, + url: 'http://localhost:8080/#', timeout: 120 * 1000, reuseExistingServer: !process.env.CI }, @@ -36,6 +36,7 @@ const config = { }, { name: 'MMOC', + testMatch: '**/*.e2e.spec.js', // only run e2e tests grepInvert: /@snapshot/, use: { browserName: 'chromium', @@ -44,20 +45,58 @@ const config = { height: 1440 } } - } - /*{ + }, + { + name: 'safari', + testMatch: '**/*.e2e.spec.js', // only run e2e tests + grep: /@ipad/, // only run ipad tests due to this bug https://github.com/microsoft/playwright/issues/8340 + grepInvert: /@snapshot/, + use: { + browserName: 'webkit' + } + }, + { + name: 'firefox', + testMatch: '**/*.e2e.spec.js', // only run e2e tests + grepInvert: /@snapshot/, + use: { + browserName: 'firefox' + } + }, + { + name: 'canary', + testMatch: '**/*.e2e.spec.js', // only run e2e tests + grepInvert: /@snapshot/, + use: { + browserName: 'chromium', + channel: 'chrome-canary' //Note this is not available in ubuntu/CircleCI + } + }, + { + name: 'chrome-beta', + testMatch: '**/*.e2e.spec.js', // only run e2e tests + grepInvert: /@snapshot/, + use: { + browserName: 'chromium', + channel: 'chrome-beta' + } + }, + { name: 'ipad', + testMatch: '**/*.e2e.spec.js', // only run e2e tests + grep: /@ipad/, + grepInvert: /@snapshot/, use: { browserName: 'webkit', ...devices['iPad (gen 7) landscape'] // Complete List https://github.com/microsoft/playwright/blob/main/packages/playwright-core/src/server/deviceDescriptorsSource.json } - }*/ + } ], reporter: [ ['list'], ['html', { open: 'on-failure', - outputFolder: '../test-results' + outputFolder: '../html-test-results' //Must be in different location due to https://github.com/microsoft/playwright/issues/12840 }] ] }; diff --git a/e2e/playwright-performance.config.js b/e2e/playwright-performance.config.js index 1d3b849ac4..e9b7e38449 100644 --- a/e2e/playwright-performance.config.js +++ b/e2e/playwright-performance.config.js @@ -4,13 +4,13 @@ /** @type {import('@playwright/test').PlaywrightTestConfig} */ const config = { - retries: 1, //Only for debugging purposes + retries: 1, //Only for debugging purposes because trace is enabled only on first retry testDir: 'tests/performance/', timeout: 60 * 1000, workers: 1, //Only run in serial with 1 worker webServer: { command: 'npm run start', - port: 8080, + url: 'http://localhost:8080/#', timeout: 200 * 1000, reuseExistingServer: !process.env.CI }, diff --git a/e2e/playwright-visual.config.js b/e2e/playwright-visual.config.js index fbf2c02011..07758121bd 100644 --- a/e2e/playwright-visual.config.js +++ b/e2e/playwright-visual.config.js @@ -10,7 +10,7 @@ const config = { workers: 1, // visual tests should never run in parallel due to test pollution webServer: { command: 'npm run start', - port: 8080, + url: 'http://localhost:8080/#', timeout: 200 * 1000, reuseExistingServer: !process.env.CI }, diff --git a/e2e/tests/branding.e2e.spec.js b/e2e/tests/branding.e2e.spec.js index dc6b94be28..a37101c8a0 100644 --- a/e2e/tests/branding.e2e.spec.js +++ b/e2e/tests/branding.e2e.spec.js @@ -58,6 +58,7 @@ test.describe('Branding tests', () => { page.waitForEvent('popup'), page.locator('text=click here for third party licensing information').click() ]); + await page2.waitForLoadState('networkidle'); //Avoids timing issues with juggler/firefox expect(page2.waitForURL('**/licenses**')).toBeTruthy(); }); }); diff --git a/e2e/tests/example/generator/SinewaveLimitProvider.e2e.spec.js b/e2e/tests/example/generator/SinewaveLimitProvider.e2e.spec.js index 24aad40a8b..972e410dfc 100644 --- a/e2e/tests/example/generator/SinewaveLimitProvider.e2e.spec.js +++ b/e2e/tests/example/generator/SinewaveLimitProvider.e2e.spec.js @@ -28,7 +28,9 @@ const { test } = require('../../../fixtures.js'); const { expect } = require('@playwright/test'); test.describe('Sine Wave Generator', () => { - test('Create new Sine Wave Generator Object and validate create Form Logic', async ({ page }) => { + test('Create new Sine Wave Generator Object and validate create Form Logic', async ({ page, browserName }) => { + test.fixme(browserName === 'firefox', 'This test needs to be updated to work with firefox'); + //Go to baseURL await page.goto('/', { waitUntil: 'networkidle' }); @@ -40,44 +42,45 @@ test.describe('Sine Wave Generator', () => { // Verify that the each required field has required indicator // Title - await expect(page.locator('.c-form-row__state-indicator').first()).toHaveClass(['c-form-row__state-indicator req']); + await expect(page.locator('.c-form-row__state-indicator').first()).toHaveClass(/req/); // Verify that the Notes row does not have a required indicator await expect(page.locator('.c-form__section div:nth-child(3) .form-row .c-form-row__state-indicator')).not.toContain('.req'); + await page.locator('textarea[type="text"]').fill('Optional Note Text'); // Period - await expect(page.locator('.c-form__section div:nth-child(4) .form-row .c-form-row__state-indicator')).toHaveClass(['c-form-row__state-indicator req']); + await expect(page.locator('div:nth-child(4) .c-form-row__state-indicator')).toHaveClass(/req/); // Amplitude - await expect(page.locator('.c-form__section div:nth-child(5) .form-row .c-form-row__state-indicator')).toHaveClass(['c-form-row__state-indicator req']); + await expect(page.locator('div:nth-child(5) .c-form-row__state-indicator')).toHaveClass(/req/); // Offset - await expect(page.locator('.c-form__section div:nth-child(6) .form-row .c-form-row__state-indicator')).toHaveClass(['c-form-row__state-indicator req']); + await expect(page.locator('div:nth-child(6) .c-form-row__state-indicator')).toHaveClass(/req/); // Data Rate - await expect(page.locator('.c-form__section div:nth-child(7) .form-row .c-form-row__state-indicator')).toHaveClass(['c-form-row__state-indicator req']); + await expect(page.locator('div:nth-child(7) .c-form-row__state-indicator')).toHaveClass(/req/); // Phase - await expect(page.locator('.c-form__section div:nth-child(8) .form-row .c-form-row__state-indicator')).toHaveClass(['c-form-row__state-indicator req']); + await expect(page.locator('div:nth-child(8) .c-form-row__state-indicator')).toHaveClass(/req/); // Randomness - await expect(page.locator('.c-form__section div:nth-child(9) .form-row .c-form-row__state-indicator')).toHaveClass(['c-form-row__state-indicator req']); + await expect(page.locator('div:nth-child(9) .c-form-row__state-indicator')).toHaveClass(/req/); // Verify that by removing value from required text field shows invalid indicator await page.locator('text=Properties Title Notes Period Amplitude Offset Data Rate (hz) Phase (radians) Ra >> input[type="text"]').fill(''); - await expect(page.locator('.c-form-row__state-indicator').first()).toHaveClass(['c-form-row__state-indicator req invalid']); + await expect(page.locator('.c-form-row__state-indicator').first()).toHaveClass(/invalid/); // Verify that by adding value to empty required text field changes invalid to valid indicator - await page.locator('text=Properties Title Notes Period Amplitude Offset Data Rate (hz) Phase (radians) Ra >> input[type="text"]').fill('non empty'); - await expect(page.locator('.c-form-row__state-indicator').first()).toHaveClass(['c-form-row__state-indicator req valid']); + await page.locator('text=Properties Title Notes Period Amplitude Offset Data Rate (hz) Phase (radians) Ra >> input[type="text"]').fill('New Sine Wave Generator'); + await expect(page.locator('.c-form-row__state-indicator').first()).toHaveClass(/valid/); // Verify that by removing value from required number field shows invalid indicator await page.locator('.field.control.l-input-sm input').first().fill(''); - await expect(page.locator('.c-form__section div:nth-child(4) .form-row .c-form-row__state-indicator')).toHaveClass(['c-form-row__state-indicator req invalid']); + await expect(page.locator('div:nth-child(4) .c-form-row__state-indicator')).toHaveClass(/invalid/); // Verify that by adding value to empty required number field changes invalid to valid indicator await page.locator('.field.control.l-input-sm input').first().fill('3'); - await expect(page.locator('.c-form__section div:nth-child(4) .form-row .c-form-row__state-indicator')).toHaveClass(['c-form-row__state-indicator req valid']); + await expect(page.locator('div:nth-child(4) .c-form-row__state-indicator')).toHaveClass(/valid/); // Verify that can change value of number field by up/down arrows keys // Click .field.control.l-input-sm input >> nth=0 @@ -90,57 +93,6 @@ test.describe('Sine Wave Generator', () => { const value = await page.locator('.field.control.l-input-sm input').first().inputValue(); await expect(value).toBe('6'); - // Click .c-form-row__state-indicator.grows - await page.locator('.c-form-row__state-indicator.grows').click(); - - // Click text=Properties Title Notes Period Amplitude Offset Data Rate (hz) Phase (radians) Ra >> input[type="text"] - await page.locator('text=Properties Title Notes Period Amplitude Offset Data Rate (hz) Phase (radians) Ra >> input[type="text"]').click(); - - // Click .c-form-row__state-indicator >> nth=0 - await page.locator('.c-form-row__state-indicator').first().click(); - - // Fill text=Properties Title Notes Period Amplitude Offset Data Rate (hz) Phase (radians) Ra >> input[type="text"] - await page.locator('text=Properties Title Notes Period Amplitude Offset Data Rate (hz) Phase (radians) Ra >> input[type="text"]').fill('New Sine Wave Generator'); - - // Double click div:nth-child(4) .form-row .c-form-row__controls - await page.locator('div:nth-child(4) .form-row .c-form-row__controls').dblclick(); - - // Click .field.control.l-input-sm input >> nth=0 - await page.locator('.field.control.l-input-sm input').first().click(); - - // Click div:nth-child(4) .form-row .c-form-row__state-indicator - await page.locator('div:nth-child(4) .form-row .c-form-row__state-indicator').click(); - - // Click .field.control.l-input-sm input >> nth=0 - await page.locator('.field.control.l-input-sm input').first().click(); - - // Click .field.control.l-input-sm input >> nth=0 - await page.locator('.field.control.l-input-sm input').first().click(); - - // Click div:nth-child(5) .form-row .c-form-row__controls .form-control .field input - await page.locator('div:nth-child(5) .form-row .c-form-row__controls .form-control .field input').click(); - - // Click div:nth-child(5) .form-row .c-form-row__controls .form-control .field input - await page.locator('div:nth-child(5) .form-row .c-form-row__controls .form-control .field input').click(); - - // Click div:nth-child(5) .form-row .c-form-row__controls .form-control .field input - await page.locator('div:nth-child(5) .form-row .c-form-row__controls .form-control .field input').click(); - - // Click div:nth-child(6) .form-row .c-form-row__controls .form-control .field input - await page.locator('div:nth-child(6) .form-row .c-form-row__controls .form-control .field input').click(); - - // Double click div:nth-child(7) .form-row .c-form-row__controls .form-control .field input - await page.locator('div:nth-child(7) .form-row .c-form-row__controls .form-control .field input').dblclick(); - - // Click div:nth-child(7) .form-row .c-form-row__state-indicator - await page.locator('div:nth-child(7) .form-row .c-form-row__state-indicator').click(); - - // Click div:nth-child(7) .form-row .c-form-row__controls .form-control .field input - await page.locator('div:nth-child(7) .form-row .c-form-row__controls .form-control .field input').click(); - - // Fill div:nth-child(7) .form-row .c-form-row__controls .form-control .field input - await page.locator('div:nth-child(7) .form-row .c-form-row__controls .form-control .field input').fill('3'); - //Click text=OK await Promise.all([ page.waitForNavigation(), @@ -151,7 +103,7 @@ test.describe('Sine Wave Generator', () => { // Verify object properties await expect(page.locator('.l-browse-bar__object-name')).toContainText('New Sine Wave Generator'); - // Verify canvas rendered + // Verify canvas rendered and can be interacted with await page.locator('canvas').nth(1).click({ position: { x: 341, diff --git a/e2e/tests/plugins/condition/condition.e2e.spec.js b/e2e/tests/plugins/condition/condition.e2e.spec.js index c7c2749068..43cd22f591 100644 --- a/e2e/tests/plugins/condition/condition.e2e.spec.js +++ b/e2e/tests/plugins/condition/condition.e2e.spec.js @@ -33,7 +33,7 @@ let conditionSetUrl; let getConditionSetIdentifierFromUrl; test.describe.serial('Condition Set CRUD Operations on @localStorage', () => { - test.beforeAll(async ({ browser }) => { + test.beforeAll(async ({ browser}) => { const context = await browser.newContext(); const page = await context.newPage(); //Go to baseURL @@ -60,6 +60,7 @@ test.describe.serial('Condition Set CRUD Operations on @localStorage', () => { getConditionSetIdentifierFromUrl = await conditionSetUrl.split('/').pop().split('?')[0]; console.debug('getConditionSetIdentifierFromUrl ' + getConditionSetIdentifierFromUrl); + await page.close(); }); test.afterAll(async ({ browser }) => { await browser.close(); @@ -67,11 +68,11 @@ test.describe.serial('Condition Set CRUD Operations on @localStorage', () => { //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', async ({ page }) => { + 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 + //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 @@ -92,7 +93,7 @@ test.describe.serial('Condition Set CRUD Operations on @localStorage', () => { test('condition set object can be modified on @localStorage', async ({ page }) => { await page.goto(conditionSetUrl, { waitUntil: 'networkidle' }); - //Assertions on loaded Condition Set in main view + //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 @@ -153,23 +154,25 @@ test.describe.serial('Condition Set CRUD Operations on @localStorage', () => { //Navigate to baseURL await page.goto('/', { waitUntil: 'networkidle' }); - const numberOfConditionSetsToStart = await page.locator('a:has-text("Unnamed Condition Set Condition Set")').count(); - //Expect Unnamed Condition Set to be visible in Main View + //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 text=Remove await page.locator('text=Remove').click(); - await page.locator('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? diff --git a/e2e/tests/plugins/imagery/exampleImagery.e2e.spec.js b/e2e/tests/plugins/imagery/exampleImagery.e2e.spec.js index 181477d457..22100fbce4 100644 --- a/e2e/tests/plugins/imagery/exampleImagery.e2e.spec.js +++ b/e2e/tests/plugins/imagery/exampleImagery.e2e.spec.js @@ -29,7 +29,10 @@ but only assume that example imagery is present. const { test } = require('../../../fixtures.js'); const { expect } = require('@playwright/test'); -test.describe('Example Imagery', () => { +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 @@ -48,28 +51,30 @@ test.describe('Example Imagery', () => { //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}); }); - const backgroundImageSelector = '.c-imagery__main-image__background-image'; test('Can use Mouse Wheel to zoom in and out of latest image', async ({ page }) => { - const bgImageLocator = page.locator(backgroundImageSelector); const deltaYStep = 100; //equivalent to 1x zoom - await bgImageLocator.hover({trial: true}); + await page.locator(backgroundImageSelector).hover({trial: true}); const originalImageDimensions = await page.locator(backgroundImageSelector).boundingBox(); // zoom in - await bgImageLocator.hover({trial: true}); + await page.locator(backgroundImageSelector).hover({trial: true}); await page.mouse.wheel(0, deltaYStep * 2); // wait for zoom animation to finish - await bgImageLocator.hover({trial: true}); + await page.locator(backgroundImageSelector).hover({trial: true}); const imageMouseZoomedIn = await page.locator(backgroundImageSelector).boundingBox(); // zoom out - await bgImageLocator.hover({trial: true}); + await page.locator(backgroundImageSelector).hover({trial: true}); await page.mouse.wheel(0, -deltaYStep); // wait for zoom animation to finish - await bgImageLocator.hover({trial: true}); + await page.locator(backgroundImageSelector).hover({trial: true}); const imageMouseZoomedOut = await page.locator(backgroundImageSelector).boundingBox(); expect(imageMouseZoomedIn.height).toBeGreaterThan(originalImageDimensions.height); @@ -83,13 +88,12 @@ test.describe('Example Imagery', () => { const deltaYStep = 100; //equivalent to 1x zoom const panHotkey = process.platform === 'linux' ? ['Control', 'Alt'] : ['Alt']; - const bgImageLocator = page.locator(backgroundImageSelector); - await bgImageLocator.hover({trial: true}); + await page.locator(backgroundImageSelector).hover({trial: true}); // zoom in await page.mouse.wheel(0, deltaYStep * 2); - await bgImageLocator.hover({trial: true}); - const zoomedBoundingBox = await bgImageLocator.boundingBox(); + 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 @@ -112,7 +116,7 @@ test.describe('Example Imagery', () => { 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 bgImageLocator.boundingBox(); + const afterRightPanBoundingBox = await page.locator(backgroundImageSelector).boundingBox(); expect(zoomedBoundingBox.x).toBeGreaterThan(afterRightPanBoundingBox.x); // pan left @@ -121,7 +125,7 @@ test.describe('Example Imagery', () => { await page.mouse.move(imageCenterX, imageCenterY, 10); await page.mouse.up(); await Promise.all(panHotkey.map(x => page.keyboard.up(x))); - const afterLeftPanBoundingBox = await bgImageLocator.boundingBox(); + const afterLeftPanBoundingBox = await page.locator(backgroundImageSelector).boundingBox(); expect(afterRightPanBoundingBox.x).toBeLessThan(afterLeftPanBoundingBox.x); // pan up @@ -131,7 +135,7 @@ test.describe('Example Imagery', () => { 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 bgImageLocator.boundingBox(); + const afterUpPanBoundingBox = await page.locator(backgroundImageSelector).boundingBox(); expect(afterUpPanBoundingBox.y).toBeGreaterThan(afterLeftPanBoundingBox.y); // pan down @@ -140,60 +144,58 @@ test.describe('Example Imagery', () => { 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 bgImageLocator.boundingBox(); + const afterDownPanBoundingBox = await page.locator(backgroundImageSelector).boundingBox(); expect(afterDownPanBoundingBox.y).toBeLessThan(afterUpPanBoundingBox.y); }); test('Can use + - buttons to zoom on the image', async ({ page }) => { - const bgImageLocator = page.locator(backgroundImageSelector); - await bgImageLocator.hover({trial: true}); + 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 bgImageLocator.boundingBox(); + const initialBoundingBox = await page.locator(backgroundImageSelector).boundingBox(); await zoomInBtn.click(); await zoomInBtn.click(); // wait for zoom animation to finish - await bgImageLocator.hover({trial: true}); - const zoomedInBoundingBox = await bgImageLocator.boundingBox(); + 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 bgImageLocator.hover({trial: true}); - const zoomedOutBoundingBox = await bgImageLocator.boundingBox(); + 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 }) => { - const bgImageLocator = page.locator(backgroundImageSelector); // wait for zoom animation to finish - await bgImageLocator.hover({trial: true}); + 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 bgImageLocator.boundingBox(); + const initialBoundingBox = await page.locator(backgroundImageSelector).boundingBox(); await zoomInBtn.click(); // wait for zoom animation to finish - await bgImageLocator.hover({trial: true}); + await page.locator(backgroundImageSelector).hover({trial: true}); await zoomInBtn.click(); // wait for zoom animation to finish - await bgImageLocator.hover({trial: true}); + await page.locator(backgroundImageSelector).hover({trial: true}); - const zoomedInBoundingBox = await bgImageLocator.boundingBox(); + const zoomedInBoundingBox = await page.locator(backgroundImageSelector).boundingBox(); expect.soft(zoomedInBoundingBox.height).toBeGreaterThan(initialBoundingBox.height); expect.soft(zoomedInBoundingBox.width).toBeGreaterThan(initialBoundingBox.width); await zoomResetBtn.click(); // wait for zoom animation to finish - await bgImageLocator.hover({trial: true}); + await page.locator(backgroundImageSelector).hover({trial: true}); - const resetBoundingBox = await bgImageLocator.boundingBox(); + const resetBoundingBox = await page.locator(backgroundImageSelector).boundingBox(); expect.soft(resetBoundingBox.height).toBeLessThan(zoomedInBoundingBox.height); expect.soft(resetBoundingBox.width).toBeLessThan(zoomedInBoundingBox.width); @@ -202,10 +204,9 @@ test.describe('Example Imagery', () => { }); test('Using the zoom features does not pause telemetry', async ({ page }) => { - const bgImageLocator = page.locator(backgroundImageSelector); const pausePlayButton = page.locator('.c-button.pause-play'); // wait for zoom animation to finish - await bgImageLocator.hover({trial: true}); + await page.locator(backgroundImageSelector).hover({trial: true}); // open the time conductor drop down await page.locator('button:has-text("Fixed Timespan")').click(); @@ -216,7 +217,7 @@ test.describe('Example Imagery', () => { const zoomInBtn = page.locator('.t-btn-zoom-in').nth(0); await zoomInBtn.click(); // wait for zoom animation to finish - await bgImageLocator.hover({trial: true}); + await page.locator(backgroundImageSelector).hover({trial: true}); return expect(pausePlayButton).not.toHaveClass(/is-paused/); }); @@ -229,8 +230,8 @@ test.describe('Example Imagery', () => { // ('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'); -const backgroundImageSelector = '.c-imagery__main-image__background-image'; -test('Example Imagery in Display layout', async ({ page }) => { +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' @@ -262,8 +263,7 @@ test('Example Imagery in Display layout', async ({ page }) => { // 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'); - const bgImageLocator = page.locator(backgroundImageSelector); - await bgImageLocator.hover({trial: true}); + await page.locator(backgroundImageSelector).hover({trial: true}); // Click previous image button const previousImageButton = page.locator('.c-nav--prev'); @@ -275,15 +275,15 @@ test('Example Imagery in Display layout', async ({ page }) => { // Zoom in const originalImageDimensions = await page.locator(backgroundImageSelector).boundingBox(); - await bgImageLocator.hover({trial: true}); + await page.locator(backgroundImageSelector).hover({trial: true}); const deltaYStep = 100; // equivalent to 1x zoom await page.mouse.wheel(0, deltaYStep * 2); - const zoomedBoundingBox = await bgImageLocator.boundingBox(); + 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 bgImageLocator.hover({trial: true}); + 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); @@ -307,11 +307,11 @@ test('Example Imagery in Display layout', async ({ page }) => { await page.locator('[data-testid=conductor-modeOption-realtime]').click(); // Zoom in on next image - await bgImageLocator.hover({trial: true}); + await page.locator(backgroundImageSelector).hover({trial: true}); await page.mouse.wheel(0, deltaYStep * 2); // Wait for zoom animation to finish - await bgImageLocator.hover({trial: true}); + 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); @@ -337,7 +337,6 @@ test('Example Imagery in Display layout', async ({ page }) => { }); 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' }); @@ -426,12 +425,137 @@ test.describe('Example imagery thumbnails resize in display layouts', () => { }); test.describe('Example Imagery in Flexible layout', () => { - 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('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'); + 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', () => { @@ -443,3 +567,185 @@ test.describe('Example Imagery in Tabs view', () => { 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); +} diff --git a/e2e/tests/plugins/notebook/restrictedNotebook.e2e.spec.js b/e2e/tests/plugins/notebook/restrictedNotebook.e2e.spec.js index 30bc169f27..37012504a0 100644 --- a/e2e/tests/plugins/notebook/restrictedNotebook.e2e.spec.js +++ b/e2e/tests/plugins/notebook/restrictedNotebook.e2e.spec.js @@ -27,108 +27,11 @@ const path = require('path'); const TEST_TEXT = 'Testing text for entries.'; const TEST_TEXT_NAME = 'Test Page'; const CUSTOM_NAME = 'CUSTOM_NAME'; -const COMMIT_BUTTON_TEXT = 'button:has-text("Commit Entries")'; -const SINE_WAVE_GENERATOR = 'text=Unnamed Sine Wave Generator'; const NOTEBOOK_DROP_AREA = '.c-notebook__drag-area'; -/** - * @param {import('@playwright/test').Page} page - */ -async function startAndAddNotebookObject(page) { - // eslint-disable-next-line no-undef - await page.addInitScript({ path: path.join(__dirname, 'addInitRestrictedNotebook.js') }); - //Go to baseURL - await page.goto('/', { waitUntil: 'networkidle' }); - //Click the Create button - await page.click('button:has-text("Create")'); - // Click text=CUSTOME_NAME - await page.click(`text=${CUSTOM_NAME}`); // secondarily tests renamability also - // Click text=OK - await Promise.all([ - page.waitForNavigation({waitUntil: 'networkidle'}), - page.click('text=OK') - ]); - - return; -} - -/** - * @param {import('@playwright/test').Page} page - */ -async function enterTextEntry(page) { - // Click .c-notebook__drag-area - await page.locator(NOTEBOOK_DROP_AREA).click(); - - // enter text - await page.locator('div.c-ne__text').click(); - await page.locator('div.c-ne__text').fill(TEST_TEXT); - await page.locator('div.c-ne__text').press('Enter'); - - return; -} - -/** - * @param {import('@playwright/test').Page} page - */ -async function dragAndDropEmbed(page) { - // Click button:has-text("Create") - await page.locator('button:has-text("Create")').click(); - // Click li:has-text("Sine Wave Generator") - await page.locator('li:has-text("Sine Wave Generator")').click(); - // Click form[name="mctForm"] >> text=My Items - await page.locator('form[name="mctForm"] >> text=My Items').click(); - // Click text=OK - await page.locator('text=OK').click(); - // Click text=Open MCT My Items >> span >> nth=3 - await page.locator('text=Open MCT My Items >> span').nth(3).click(); - // Click text=Unnamed CUSTOM_NAME - await Promise.all([ - page.waitForNavigation(), - page.locator('text=Unnamed CUSTOM_NAME').click() - ]); - - await page.dragAndDrop(SINE_WAVE_GENERATOR, NOTEBOOK_DROP_AREA); - - return; -} - -/** - * @param {import('@playwright/test').Page} page - */ -async function lockPage(page) { - const commitButton = page.locator(COMMIT_BUTTON_TEXT); - await commitButton.click(); - - // confirmation dialog click - await page.locator('text=Lock Page').click(); - - // waiting for mutation of locked page - await new Promise((resolve, reject) => { - setTimeout(resolve, 1000); - }); - - return; -} - -/** - * @param {import('@playwright/test').Page} page - */ -async function openContextMenuRestrictedNotebook(page) { - // Click text=Open MCT My Items (This expands the My Items folder to show it's chilren in the tree) - await page.locator('text=Open MCT My Items >> span').nth(3).click(); - - // Click a:has-text("Unnamed CUSTOM_NAME") - await page.locator(`a:has-text("Unnamed ${CUSTOM_NAME}")`).click({ - button: 'right' - }); - - return; -} - -test.describe('Restricted Notebook @addInit', () => { - +test.describe('Restricted Notebook', () => { test.beforeEach(async ({ page }) => { - await startAndAddNotebookObject(page); + await startAndAddRestrictedNotebookObject(page); }); test('Can be renamed @addInit', async ({ page }) => { @@ -146,13 +49,16 @@ test.describe('Restricted Notebook @addInit', () => { // notbook tree object exists expect.soft(await restrictedNotebookTreeObject.count()).toEqual(1); - // Click text=Remove + // Click Remove Text await page.locator('text=Remove').click(); - // Click text=OK + + //Wait until Save Banner is gone await Promise.all([ - page.waitForNavigation(/*{ url: 'http://localhost:8080/#/browse/mine?tc.mode=fixed&tc.startBound=1653671067340&tc.endBound=1653672867340&tc.timeSystem=utc&view=grid' }*/), - page.locator('text=OK').click() + page.waitForNavigation(), + page.locator('text=OK').click(), + page.waitForSelector('.c-message-banner__message') ]); + await page.locator('.c-message-banner__close-button').click(); // has been deleted expect.soft(await restrictedNotebookTreeObject.count()).toEqual(0); @@ -162,7 +68,7 @@ test.describe('Restricted Notebook @addInit', () => { await enterTextEntry(page); - const commitButton = page.locator(COMMIT_BUTTON_TEXT); + const commitButton = page.locator('button:has-text("Commit Entries")'); expect.soft(await commitButton.count()).toEqual(1); }); @@ -171,7 +77,7 @@ test.describe('Restricted Notebook @addInit', () => { test.describe('Restricted Notebook with at least one entry and with the page locked @addInit', () => { test.beforeEach(async ({ page }) => { - await startAndAddNotebookObject(page); + await startAndAddRestrictedNotebookObject(page); await enterTextEntry(page); await lockPage(page); @@ -218,7 +124,7 @@ test.describe('Restricted Notebook with at least one entry and with the page loc await enterTextEntry(page); // expect new page to be lockable - const commitButton = page.locator(COMMIT_BUTTON_TEXT); + const commitButton = page.locator('BUTTON:HAS-TEXT("COMMIT ENTRIES")'); expect.soft(await commitButton.count()).toEqual(1); // Click text=Unnamed PageTest Page >> button @@ -240,7 +146,7 @@ test.describe('Restricted Notebook with at least one entry and with the page loc test.describe('Restricted Notebook with a page locked and with an embed @addInit', () => { test.beforeEach(async ({ page }) => { - await startAndAddNotebookObject(page); + await startAndAddRestrictedNotebookObject(page); await dragAndDropEmbed(page); }); @@ -262,3 +168,95 @@ test.describe('Restricted Notebook with a page locked and with an embed @addInit }); }); + +/** + * @param {import('@playwright/test').Page} page + */ +async function startAndAddRestrictedNotebookObject(page) { + // eslint-disable-next-line no-undef + await page.addInitScript({ path: path.join(__dirname, 'addInitRestrictedNotebook.js') }); + //Go to baseURL + await page.goto('/', { waitUntil: 'networkidle' }); + //Click the Create button + await page.click('button:has-text("Create")'); + // Click text=CUSTOME_NAME + await page.click(`text=${CUSTOM_NAME}`); // secondarily tests renamability also + // Click text=OK + await Promise.all([ + page.waitForNavigation({waitUntil: 'networkidle'}), + page.click('text=OK') + ]); +} + +/** + * @param {import('@playwright/test').Page} page + */ +async function enterTextEntry(page) { + // Click .c-notebook__drag-area + await page.locator(NOTEBOOK_DROP_AREA).click(); + + // enter text + await page.locator('div.c-ne__text').click(); + await page.locator('div.c-ne__text').fill(TEST_TEXT); + await page.locator('div.c-ne__text').press('Enter'); +} + +/** + * @param {import('@playwright/test').Page} page + */ +async function dragAndDropEmbed(page) { + // Click button:has-text("Create") + await page.locator('button:has-text("Create")').click(); + // Click li:has-text("Sine Wave Generator") + await page.locator('li:has-text("Sine Wave Generator")').click(); + // Click form[name="mctForm"] >> text=My Items + await page.locator('form[name="mctForm"] >> text=My Items').click(); + // Click text=OK + await page.locator('text=OK').click(); + // Click text=Open MCT My Items >> span >> nth=3 + await page.locator('text=Open MCT My Items >> span').nth(3).click(); + // Click text=Unnamed CUSTOM_NAME + await Promise.all([ + page.waitForNavigation(), + page.locator('text=Unnamed CUSTOM_NAME').click() + ]); + + await page.dragAndDrop('text=UNNAMED SINE WAVE GENERATOR', NOTEBOOK_DROP_AREA); +} + +/** + * @param {import('@playwright/test').Page} page + */ +async function lockPage(page) { + const commitButton = page.locator('button:has-text("Commit Entries")'); + await commitButton.click(); + + //Wait until Lock Banner is visible + await Promise.all([ + page.locator('text=Lock Page').click(), + page.waitForSelector('.c-message-banner__message') + ]); + // Close Lock Banner + await page.locator('.c-message-banner__close-button').click(); + + //artifically wait to avoid mutation delay TODO: https://github.com/nasa/openmct/issues/5409 + // eslint-disable-next-line playwright/no-wait-for-timeout + await page.waitForTimeout(1 * 1000); +} + +/** + * @param {import('@playwright/test').Page} page + */ +async function openContextMenuRestrictedNotebook(page) { + // Click text=Open MCT My Items (This expands the My Items folder to show it's chilren in the tree) + await page.locator('text=Open MCT My Items >> span').nth(3).click(); + + //artifically wait to avoid mutation delay TODO: https://github.com/nasa/openmct/issues/5409 + // eslint-disable-next-line playwright/no-wait-for-timeout + await page.waitForTimeout(1 * 1000); + + // Click a:has-text("Unnamed CUSTOM_NAME") + await page.locator(`a:has-text("Unnamed ${CUSTOM_NAME}")`).click({ + button: 'right' + }); +} diff --git a/e2e/tests/plugins/plot/autoscale.e2e.spec.js b/e2e/tests/plugins/plot/autoscale.e2e.spec.js index c6f1b6cf75..ee9782ec42 100644 --- a/e2e/tests/plugins/plot/autoscale.e2e.spec.js +++ b/e2e/tests/plugins/plot/autoscale.e2e.spec.js @@ -24,22 +24,9 @@ Testsuite for plot autoscale. */ -const { test: _test } = require('../../../fixtures.js'); +const { test } = require('../../../fixtures.js'); const { expect } = require('@playwright/test'); -// create a new `test` API that will not append platform details to snapshot -// file names, only for the tests in this file, so that the same snapshots will -// be used for all platforms. -const test = _test.extend({ - _autoSnapshotSuffix: [ - async ({}, use, testInfo) => { - testInfo.snapshotSuffix = ''; - await use(); - }, - { auto: true } - ] -}); - test.use({ viewport: { width: 1280, @@ -50,7 +37,7 @@ test.use({ test.describe('ExportAsJSON', () => { test('User can set autoscale with a valid range @snapshot', async ({ page }) => { //This is necessary due to the size of the test suite. - await test.setTimeout(120 * 1000); + test.slow(); await page.goto('/', { waitUntil: 'networkidle' }); @@ -62,16 +49,16 @@ test.describe('ExportAsJSON', () => { await turnOffAutoscale(page); + // Make sure that after turning off autoscale, the user selected range values start at the same values the plot had prior. + await testYTicks(page, ['-1.00', '-0.50', '0.00', '0.50', '1.00']); + const canvas = page.locator('canvas').nth(1); - // Make sure that after turning off autoscale, the user selected range values start at the same values the plot had prior. - await Promise.all([ - testYTicks(page, ['-1.00', '-0.50', '0.00', '0.50', '1.00']), - new Promise(r => setTimeout(r, 100)) - .then(() => canvas.screenshot()) - .then(shot => expect(shot).toMatchSnapshot('autoscale-canvas-prepan.png', { maxDiffPixels: 40 })) - ]); + await canvas.hover({trial: true}); + expect(await canvas.screenshot()).toMatchSnapshot('autoscale-canvas-prepan'); + + //Alt Drag Start await page.keyboard.down('Alt'); await canvas.dragTo(canvas, { @@ -85,15 +72,15 @@ test.describe('ExportAsJSON', () => { } }); + //Alt Drag End await page.keyboard.up('Alt'); // Ensure the drag worked. - await Promise.all([ - testYTicks(page, ['0.00', '0.50', '1.00', '1.50', '2.00']), - new Promise(r => setTimeout(r, 100)) - .then(() => canvas.screenshot()) - .then(shot => expect(shot).toMatchSnapshot('autoscale-canvas-panned.png', { maxDiffPixels: 40 })) - ]); + await testYTicks(page, ['0.00', '0.50', '1.00', '1.50', '2.00']); + + await canvas.hover({trial: true}); + + expect(await canvas.screenshot()).toMatchSnapshot('autoscale-canvas-panned'); }); }); diff --git a/e2e/tests/plugins/plot/autoscale.e2e.spec.js-snapshots/autoscale-canvas-panned-chrome-darwin b/e2e/tests/plugins/plot/autoscale.e2e.spec.js-snapshots/autoscale-canvas-panned-chrome-darwin new file mode 100644 index 0000000000..01850a3bc4 Binary files /dev/null and b/e2e/tests/plugins/plot/autoscale.e2e.spec.js-snapshots/autoscale-canvas-panned-chrome-darwin differ diff --git a/e2e/tests/plugins/plot/autoscale.e2e.spec.js-snapshots/autoscale-canvas-panned-chrome-linux b/e2e/tests/plugins/plot/autoscale.e2e.spec.js-snapshots/autoscale-canvas-panned-chrome-linux new file mode 100644 index 0000000000..7fb1ec390d Binary files /dev/null and b/e2e/tests/plugins/plot/autoscale.e2e.spec.js-snapshots/autoscale-canvas-panned-chrome-linux differ diff --git a/e2e/tests/plugins/plot/autoscale.e2e.spec.js-snapshots/autoscale-canvas-panned-chrome.png b/e2e/tests/plugins/plot/autoscale.e2e.spec.js-snapshots/autoscale-canvas-panned-chrome.png deleted file mode 100644 index 4b546e1878..0000000000 Binary files a/e2e/tests/plugins/plot/autoscale.e2e.spec.js-snapshots/autoscale-canvas-panned-chrome.png and /dev/null differ diff --git a/e2e/tests/plugins/plot/autoscale.e2e.spec.js-snapshots/autoscale-canvas-prepan-chrome-darwin b/e2e/tests/plugins/plot/autoscale.e2e.spec.js-snapshots/autoscale-canvas-prepan-chrome-darwin new file mode 100644 index 0000000000..75b1c4d953 Binary files /dev/null and b/e2e/tests/plugins/plot/autoscale.e2e.spec.js-snapshots/autoscale-canvas-prepan-chrome-darwin differ diff --git a/e2e/tests/plugins/plot/autoscale.e2e.spec.js-snapshots/autoscale-canvas-prepan-chrome-linux b/e2e/tests/plugins/plot/autoscale.e2e.spec.js-snapshots/autoscale-canvas-prepan-chrome-linux new file mode 100644 index 0000000000..031025d8e0 Binary files /dev/null and b/e2e/tests/plugins/plot/autoscale.e2e.spec.js-snapshots/autoscale-canvas-prepan-chrome-linux differ diff --git a/e2e/tests/plugins/plot/autoscale.e2e.spec.js-snapshots/autoscale-canvas-prepan-chrome.png b/e2e/tests/plugins/plot/autoscale.e2e.spec.js-snapshots/autoscale-canvas-prepan-chrome.png deleted file mode 100644 index c0f293bd16..0000000000 Binary files a/e2e/tests/plugins/plot/autoscale.e2e.spec.js-snapshots/autoscale-canvas-prepan-chrome.png and /dev/null differ diff --git a/e2e/tests/plugins/plot/logPlot.e2e.spec.js b/e2e/tests/plugins/plot/logPlot.e2e.spec.js index 610efe7cc4..798109a5d8 100644 --- a/e2e/tests/plugins/plot/logPlot.e2e.spec.js +++ b/e2e/tests/plugins/plot/logPlot.e2e.spec.js @@ -30,8 +30,8 @@ const { expect } = require('@playwright/test'); test.describe('Log plot tests', () => { test('Log Plot ticks are functionally correct in regular and log mode and after refresh', async ({ page }) => { - //This is necessary due to the size of the test suite. - await test.setTimeout(120 * 1000); + //Test.slow decorator is currently broken. Needs to be fixed in https://github.com/nasa/openmct/issues/5374 + test.slow(); await makeOverlayPlot(page); await testRegularTicks(page); @@ -44,20 +44,6 @@ test.describe('Log plot tests', () => { await testLogTicks(page); await saveOverlayPlot(page); await testLogTicks(page); - //await testLogPlotPixels(page); - - // FIXME: Get rid of the waitForTimeout() and lint warning exception. - // eslint-disable-next-line playwright/no-wait-for-timeout - await page.waitForTimeout(1 * 1000); - - // refresh page and wait for charts and ticks to load - await page.reload({ waitUntil: 'networkidle'}); - await page.waitForSelector('.gl-plot-chart-area'); - await page.waitForSelector('.gl-plot-y-tick-label'); - - // test log ticks hold up after refresh - await testLogTicks(page); - //await testLogPlotPixels(page); }); // Leaving test as 'TODO' for now. @@ -121,14 +107,14 @@ async function makeOverlayPlot(page) { // set amplitude to 6, offset 4, period 2 - await page.locator('div:nth-child(5) .form-row .c-form-row__controls .form-control .field input').click(); - await page.locator('div:nth-child(5) .form-row .c-form-row__controls .form-control .field input').fill('6'); + await page.locator('div:nth-child(5) .c-form-row__controls .form-control .field input').click(); + await page.locator('div:nth-child(5) .c-form-row__controls .form-control .field input').fill('6'); - await page.locator('div:nth-child(6) .form-row .c-form-row__controls .form-control .field input').click(); - await page.locator('div:nth-child(6) .form-row .c-form-row__controls .form-control .field input').fill('4'); + await page.locator('div:nth-child(6) .c-form-row__controls .form-control .field input').click(); + await page.locator('div:nth-child(6) .c-form-row__controls .form-control .field input').fill('4'); - await page.locator('div:nth-child(7) .form-row .c-form-row__controls .form-control .field input').click(); - await page.locator('div:nth-child(7) .form-row .c-form-row__controls .form-control .field input').fill('2'); + await page.locator('div:nth-child(7) .c-form-row__controls .form-control .field input').click(); + await page.locator('div:nth-child(7) .c-form-row__controls .form-control .field input').fill('2'); // Click OK to make generator diff --git a/e2e/tests/recycled_storage.json b/e2e/tests/recycled_storage.json index 53c7695977..8d59148b23 100644 --- a/e2e/tests/recycled_storage.json +++ b/e2e/tests/recycled_storage.json @@ -4,19 +4,19 @@ { "origin": "http://localhost:8080", "localStorage": [ + { + "name": "mct-tree-expanded", + "value": "[]" + }, { "name": "tcHistory", - "value": "{\"utc\":[{\"start\":1652301954635,\"end\":1652303754635}]}" + "value": "{\"utc\":[{\"start\":1656473493306,\"end\":1656475293306},{\"start\":1655769110258,\"end\":1655770910258},{\"start\":1652301954635,\"end\":1652303754635}]}" }, { "name": "mct", - "value": "{\"mine\":{\"identifier\":{\"key\":\"mine\",\"namespace\":\"\"},\"name\":\"My Items\",\"type\":\"folder\",\"composition\":[{\"key\":\"f64bea3b-58a7-4586-8c05-8b651e5f0bfd\",\"namespace\":\"\"}],\"location\":\"ROOT\",\"persisted\":1652303756008,\"modified\":1652303756007},\"f64bea3b-58a7-4586-8c05-8b651e5f0bfd\":{\"name\":\"Unnamed Condition Set\",\"type\":\"conditionSet\",\"identifier\":{\"key\":\"f64bea3b-58a7-4586-8c05-8b651e5f0bfd\",\"namespace\":\"\"},\"configuration\":{\"conditionTestData\":[],\"conditionCollection\":[{\"isDefault\":true,\"id\":\"73f2d9ae-d1f3-4561-b7fc-ecd5df557249\",\"configuration\":{\"name\":\"Default\",\"output\":\"Default\",\"trigger\":\"all\",\"criteria\":[]},\"summary\":\"Default condition\"}]},\"composition\":[],\"telemetry\":{},\"modified\":1652303755999,\"location\":\"mine\",\"persisted\":1652303756002}}" - }, - { - "name": "mct-tree-expanded", - "value": "[]" + "value": "{\"mine\":{\"identifier\":{\"key\":\"mine\",\"namespace\":\"\"},\"name\":\"My Items\",\"type\":\"folder\",\"composition\":[{\"key\":\"f64bea3b-58a7-4586-8c05-8b651e5f0bfd\",\"namespace\":\"\"},{\"key\":\"18ba28bf-152e-4e0f-9b9c-638fb2ade0c3\",\"namespace\":\"\"},{\"key\":\"fa64bd6c-9351-4d94-a54e-e062a93be3b6\",\"namespace\":\"\"}],\"location\":\"ROOT\",\"persisted\":1656475294042,\"modified\":1656475294042},\"f64bea3b-58a7-4586-8c05-8b651e5f0bfd\":{\"name\":\"Unnamed Condition Set\",\"type\":\"conditionSet\",\"identifier\":{\"key\":\"f64bea3b-58a7-4586-8c05-8b651e5f0bfd\",\"namespace\":\"\"},\"configuration\":{\"conditionTestData\":[],\"conditionCollection\":[{\"isDefault\":true,\"id\":\"73f2d9ae-d1f3-4561-b7fc-ecd5df557249\",\"configuration\":{\"name\":\"Default\",\"output\":\"Default\",\"trigger\":\"all\",\"criteria\":[]},\"summary\":\"Default condition\"}]},\"composition\":[],\"telemetry\":{},\"modified\":1652303755999,\"location\":\"mine\",\"persisted\":1652303756002},\"18ba28bf-152e-4e0f-9b9c-638fb2ade0c3\":{\"name\":\"Unnamed Condition Set\",\"type\":\"conditionSet\",\"identifier\":{\"key\":\"18ba28bf-152e-4e0f-9b9c-638fb2ade0c3\",\"namespace\":\"\"},\"configuration\":{\"conditionTestData\":[],\"conditionCollection\":[{\"isDefault\":true,\"id\":\"43cfb4b1-348c-43c0-a681-c4cf53b5335f\",\"configuration\":{\"name\":\"Default\",\"output\":\"Default\",\"trigger\":\"all\",\"criteria\":[]},\"summary\":\"Default condition\"}]},\"composition\":[],\"telemetry\":{},\"modified\":1655770911020,\"location\":\"mine\",\"persisted\":1655770911020},\"fa64bd6c-9351-4d94-a54e-e062a93be3b6\":{\"name\":\"Unnamed Condition Set\",\"type\":\"conditionSet\",\"identifier\":{\"key\":\"fa64bd6c-9351-4d94-a54e-e062a93be3b6\",\"namespace\":\"\"},\"configuration\":{\"conditionTestData\":[],\"conditionCollection\":[{\"isDefault\":true,\"id\":\"26739ce0-9a56-466c-91dd-f08bd9bfc9d7\",\"configuration\":{\"name\":\"Default\",\"output\":\"Default\",\"trigger\":\"all\",\"criteria\":[]},\"summary\":\"Default condition\"}]},\"composition\":[],\"telemetry\":{},\"modified\":1656475294040,\"location\":\"mine\",\"persisted\":1656475294040}}" } ] } ] -} +} \ No newline at end of file diff --git a/e2e/tests/smoke.e2e.spec.js b/e2e/tests/smoke.e2e.spec.js index 43e73972f5..f8f87794f7 100644 --- a/e2e/tests/smoke.e2e.spec.js +++ b/e2e/tests/smoke.e2e.spec.js @@ -45,6 +45,15 @@ test('Verify that the create button appears and that the Folder Domain Object is await page.click('button:has-text("Create")'); // Verify that Create Folder appears in the dropdown - const locator = page.locator(':nth-match(:text("Folder"), 2)'); - await expect(locator).toBeEnabled(); + await expect(page.locator(':nth-match(:text("Folder"), 2)')).toBeEnabled(); +}); + +test('Verify that My Items Tree appears @ipad', async ({ page }) => { + //Test.slow annotation is currently broken. Needs to be fixed in https://github.com/nasa/openmct/issues/5374 + test.slow(); + //Go to baseURL + await page.goto('/'); + + //My Items to be visible + await expect(page.locator('a:has-text("My Items")')).toBeEnabled(); }); diff --git a/e2e/tests/visual/default.visual.spec.js b/e2e/tests/visual/default.visual.spec.js index b069f2e95f..e64de2925e 100644 --- a/e2e/tests/visual/default.visual.spec.js +++ b/e2e/tests/visual/default.visual.spec.js @@ -32,7 +32,8 @@ to "fail" on assertions. Instead, they should be used to detect changes between Note: Larger testsuite sizes are OK due to the setup time associated with these tests. */ -const { test, expect } = require('@playwright/test'); +const { test } = require('../../fixtures.js'); +const { expect } = require('@playwright/test'); const percySnapshot = require('@percy/playwright'); const path = require('path'); const sinon = require('sinon'); @@ -96,7 +97,11 @@ test('Visual - Default Condition Set', async ({ page }) => { await percySnapshot(page, 'Default Condition Set'); }); -test('Visual - Default Condition Widget', async ({ page }) => { +test.fixme('Visual - Default Condition Widget', async ({ page }) => { + test.info().annotations.push({ + type: 'issue', + description: 'https://github.com/nasa/openmct/issues/5349' + }); //Go to baseURL await page.goto('/', { waitUntil: 'networkidle' }); diff --git a/karma.conf.js b/karma.conf.js index e5fbe40ab0..e07aa94671 100644 --- a/karma.conf.js +++ b/karma.conf.js @@ -74,13 +74,8 @@ module.exports = (config) => { }, coverageIstanbulReporter: { fixWebpackSourcePaths: true, - dir: "dist/reports/coverage", - reports: ['lcovonly', 'text-summary'], - thresholds: { - global: { - lines: 52 - } - } + dir: "coverage/unit", + reports: ['lcovonly'] }, specReporter: { maxLogLines: 5, diff --git a/package.json b/package.json index 4439b37a68..5e11492516 100644 --- a/package.json +++ b/package.json @@ -7,7 +7,7 @@ "@braintree/sanitize-url": "6.0.0", "@percy/cli": "1.2.1", "@percy/playwright": "1.0.4", - "@playwright/test": "1.21.1", + "@playwright/test": "1.23.0", "@types/eventemitter3": "^1.0.0", "@types/jasmine": "^4.0.1", "@types/karma": "^6.3.2", @@ -16,6 +16,7 @@ "babel-loader": "8.2.3", "babel-plugin-istanbul": "6.1.1", "comma-separated-values": "3.6.4", + "codecov":"3.8.3", "copy-webpack-plugin": "11.0.0", "cross-env": "7.0.3", "css-loader": "4.0.0", @@ -55,6 +56,7 @@ "moment-timezone": "0.5.34", "node-bourbon": "4.2.3", "painterro": "1.2.56", + "nyc":"15.1.0", "plotly.js-basic-dist": "2.12.0", "plotly.js-gl2d-dist": "2.12.0", "printj": "1.3.1", @@ -89,9 +91,10 @@ "test": "cross-env NODE_OPTIONS=\"--max_old_space_size=4096\" karma start --single-run", "test:firefox": "cross-env NODE_OPTIONS=\"--max_old_space_size=4096\" karma start --single-run --browsers=FirefoxHeadless", "test:debug": "cross-env NODE_ENV=debug karma start --no-single-run", - "test:e2e:ci": "npx playwright test --config=e2e/playwright-ci.config.js --project=chrome smoke branding default condition timeConductor clock exampleImagery persistence performance grandsearch tags", + "test:e2e": "npx playwright test", + "test:e2e:ci": "npx playwright test --config=e2e/playwright-ci.config.js --project=chrome smoke branding default condition timeConductor clock exampleImagery persistence performance grandsearch notebook/tags", "test:e2e:local": "npx playwright test --config=e2e/playwright-local.config.js --project=chrome", - "test:e2e:updatesnapshots": "npx playwright test --config=e2e/playwright-local.config.js --project=chrome --grep @snapshot --update-snapshots", + "test:e2e:updatesnapshots": "npx playwright test --config=e2e/playwright-ci.config.js --project=chrome --grep @snapshot --update-snapshots", "test:e2e:visual": "percy exec --config ./e2e/.percy.yml -- npx playwright test --config=e2e/playwright-visual.config.js", "test:e2e:full": "npx playwright test --config=e2e/playwright-ci.config.js", "test:perf": "npx playwright test --config=e2e/playwright-performance.config.js", @@ -101,6 +104,10 @@ "update-copyright-date": "npm run update-about-dialog-copyright && grep -lr --null --include=*.{js,scss,vue,ts,sh,html,md,frag} 'Copyright (c) 20' . | xargs -r0 perl -pi -e 's/Copyright\\s\\(c\\)\\s20\\d\\d\\-20\\d\\d/Copyright \\(c\\)\\ 2014\\-2022/gm'", "otherdoc": "node docs/gendocs.js --in docs/src --out dist/docs --suppress-toc 'docs/src/index.md|docs/src/process/index.md'", "docs": "npm run jsdoc ; npm run otherdoc", + "cov:e2e:report":"nyc report --reporter=lcovonly --report-dir=./coverage/e2e", + "cov:e2e:full:publish":"codecov --disable=gcov -f ./coverage/e2e/lcov.info -F e2e-full", + "cov:e2e:ci:publish":"codecov --disable=gcov -f ./coverage/e2e/lcov.info -F e2e-ci", + "cov:unit:publish":"codecov --disable=gcov -f ./coverage/unit/lcov.info -F unit", "prepare": "npm run build:prod" }, "repository": { diff --git a/webpack.common.js b/webpack.common.js index 38e9e8644d..1c938af845 100644 --- a/webpack.common.js +++ b/webpack.common.js @@ -94,8 +94,13 @@ const config = { { loader: 'css-loader' }, - 'resolve-url-loader', - 'sass-loader' + { + loader: 'resolve-url-loader' + }, + { + loader: 'sass-loader', + options: {sourceMap: true } + } ] }, { diff --git a/webpack.coverage.js b/webpack.coverage.js index 94766eb6c8..478428bcd3 100644 --- a/webpack.coverage.js +++ b/webpack.coverage.js @@ -34,6 +34,7 @@ config.module.rules.push({ use: { loader: 'babel-loader', options: { + // eslint-disable-next-line no-undef configFile: path.resolve(process.cwd(), 'babel.coverage.js') } }