[Mobile] Add Global Search to Mobile (#7477)

* Add status area back to mobile

* Make search results responsive to width

* Make clear search button always visible, regardless of hover

* Make clear search button visible, and fix weird margin in top left corner

* Fix input width, add logic to make close button work properly, fix margin on results so there is room for close button, fix various landscape mode issues

* update mobile testing

* Fix dropdown sizes, remove shadows and corners to make it look less like a popup and more full screen

* Add animation and persist search bar in mobile

* Fix linting issues

* Fix padding in Desktop

* fix padding in status area

* fix bad merge

* lint fixes

* fix bad merge... again

* again

* fixes to mobile

* update tests

* lint fix

* test fixes

---------

Co-authored-by: John Hill <john.c.hill@nasa.gov>
This commit is contained in:
Rukmini Bose (Ruki) 2024-02-21 15:29:38 -08:00 committed by GitHub
parent 6bbabf9c45
commit 6393a77c19
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
17 changed files with 304 additions and 42 deletions

View File

@ -160,6 +160,31 @@ jobs:
equal: [42, 42] # Always generate version artifacts regardless of test failure https://discuss.circleci.com/t/make-custom-command-run-always-with-when-always/38957/2
steps:
- generate_and_store_version_and_filesystem_artifacts
e2e-mobile:
executor: pw-focal-development
steps:
- build_and_install:
node-version: lts/hydrogen
- run: npm run test:e2e:mobile
- when:
condition:
equal: [42, 42] # Always run codecov reports regardless of test failure https://discuss.circleci.com/t/make-custom-command-run-always-with-when-always/38957/2
steps:
- generate_e2e_code_cov_report:
suite: full
- store_test_results:
path: test-results/results.xml
- store_artifacts:
path: test-results
- store_artifacts:
path: coverage
- store_artifacts:
path: html-test-results
- when:
condition:
equal: [42, 42] # Always generate version artifacts regardless of test failure https://discuss.circleci.com/t/make-custom-command-run-always-with-when-always/38957/2
steps:
- generate_and_store_version_and_filesystem_artifacts
e2e-couchdb:
executor: ubuntu
steps:
@ -260,8 +285,9 @@ workflows:
- e2e-test:
name: e2e-stable
suite: stable
- e2e-mobile
- visual-a11y-tests:
name: visual-test-ci
name: visual-a11y-test-ci
suite: ci
the-nightly: #These jobs do not run on PRs, but against master at night
@ -277,10 +303,11 @@ workflows:
- e2e-test:
name: e2e-full-nightly
suite: full
- mem-test
- e2e-mobile
- perf-test
- mem-test
- visual-a11y-tests:
name: visual-test-nightly
name: visual-a11y-test-nightly
suite: full
- e2e-couchdb
triggers:

View File

@ -229,7 +229,7 @@ Current list of test tags:
|Test Tag|Description|
|:-:|-|
|`@ipad` | Test case or test suite is compatible with Playwright's iPad support and Open MCT's read-only mobile view (i.e. no create button).|
|`@mobile` | Test case or test suite is compatible with Playwright's iPad support and Open MCT's read-only mobile view (i.e. no create button).|
|`@a11y` | Test case or test suite to execute playwright-axe accessibility checks and generate a11y reports.|
|`@gds` | Denotes a GDS Test Case used in the VIPER Mission.|
|`@addInit` | Initializes the browser with an injected and artificial state. Useful for loading non-default plugins. Likely will not work outside of `npm start`.|
@ -329,9 +329,15 @@ In terms of operating system testing, we're only limited by what the CI provider
#### **Mobile**
We have the Mission-need to support iPad. To run our iPad suite, please see our `playwright-*.config.js` with the 'iPad' project.
We have a Mission-need to support iPad and mobile devices. To run our test suites with mobile devices, please see our `playwright-mobile.config.js` projects.
In general, our test suite is not designed to run against mobile devices as the mobile experience is a focused version of the application. Core functionality is missing (chiefly the 'Create' button) and so this will likely turn into a separate suite.
In general, our test suite is not designed to run against mobile devices as the mobile experience is a focused version of the application. Core functionality is missing (chiefly the 'Create' button). To bypass the object creation, we leverage the `storageState` properties for starting the mobile tests with localstorage.
For now, the mobile tests will exist in the /tests/mobile/ suites and be executed with the
```sh
npm run test:e2e:mobile
```
command.
#### **Skipping or executing tests based on browser, os, and/os browser version:**

View File

@ -10,6 +10,7 @@ const NUM_WORKERS = 2;
const config = {
retries: 2, //Retries 2 times for a total of 3 runs. When running sharded and with max-failures=5, this should ensure that flake is managed without failing the full suite
testDir: 'tests',
grepInvert: /@mobile/, //Ignore mobile tests
testIgnore: '**/*.perf.spec.js', //Ignore performance tests and define in playwright-perfromance.config.js
timeout: 60 * 1000,
webServer: {

View File

@ -1,13 +1,11 @@
// playwright.config.js
// @ts-check
// eslint-disable-next-line no-unused-vars
import { devices } from '@playwright/test';
/** @type {import('@playwright/test').PlaywrightTestConfig} */
const config = {
retries: 0,
testDir: 'tests',
testMatch: '**/*.e2e.spec.js', // only run e2e tests
testIgnore: '**/*.perf.spec.js',
timeout: 30 * 1000,
webServer: {
@ -35,7 +33,6 @@ const config = {
},
{
name: 'MMOC',
testMatch: '**/*.e2e.spec.js', // only run e2e tests
grepInvert: /@snapshot/,
use: {
browserName: 'chromium',
@ -47,8 +44,6 @@ const config = {
},
{
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'
@ -56,7 +51,6 @@ const config = {
},
{
name: 'firefox',
testMatch: '**/*.e2e.spec.js', // only run e2e tests
grepInvert: /@snapshot/,
use: {
browserName: 'firefox'
@ -64,7 +58,6 @@ const config = {
},
{
name: 'canary',
testMatch: '**/*.e2e.spec.js', // only run e2e tests
grepInvert: /@snapshot/,
use: {
browserName: 'chromium',
@ -73,22 +66,11 @@ const config = {
},
{
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: [

View File

@ -0,0 +1,69 @@
// playwright.config.js
// @ts-check
import { devices } from '@playwright/test';
const MAX_FAILURES = 5;
const NUM_WORKERS = 2;
import { fileURLToPath } from 'url';
/** @type {import('@playwright/test').PlaywrightTestConfig} */
const config = {
retries: 1, //Retries 2 times for a total of 3 runs. When running sharded and with max-failures=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: 30 * 1000,
webServer: {
command: 'npm run start:coverage',
url: 'http://localhost:8080/#',
timeout: 200 * 1000,
reuseExistingServer: true //This was originally disabled to prevent differences in local debugging vs. CI. However, it significantly speeds up local debugging.
},
maxFailures: MAX_FAILURES, //Limits failures to 5 to reduce CI Waste
workers: NUM_WORKERS, //Limit to 2 for CircleCI Agent
use: {
baseURL: 'http://localhost:8080/',
headless: true,
ignoreHTTPSErrors: true,
screenshot: 'only-on-failure',
trace: 'on-first-retry',
video: 'off'
},
projects: [
{
name: 'ipad',
grep: /@mobile/,
use: {
storageState: fileURLToPath(
new URL('./test-data/display_layout_with_child_layouts.json', import.meta.url)
),
browserName: 'webkit',
...devices['iPad (gen 7) landscape'] // Complete List https://github.com/microsoft/playwright/blob/main/packages/playwright-core/src/server/deviceDescriptorsSource.json
}
},
{
name: 'iphone',
grep: /@mobile/,
use: {
storageState: fileURLToPath(
new URL('./test-data/display_layout_with_child_layouts.json', import.meta.url)
),
browserName: 'webkit',
...devices['iPhone 14 Pro'] // Complete List https://github.com/microsoft/playwright/blob/main/packages/playwright-core/src/server/deviceDescriptorsSource.json
}
}
],
reporter: [
['list'],
[
'html',
{
open: 'never',
outputFolder: '../html-test-results' //Must be in different location due to https://github.com/microsoft/playwright/issues/12840
}
],
['junit', { outputFile: '../test-results/results.xml' }]
]
};
export default config;

View File

@ -1,6 +1,9 @@
// playwright.config.js
// @ts-check
import { devices } from '@playwright/test';
import { fileURLToPath } from 'url';
/** @type {import('@playwright/test').PlaywrightTestConfig} */
const config = {
retries: 0, //Retries are not needed with watch mode
@ -28,6 +31,28 @@ const config = {
use: {
browserName: 'chromium'
}
},
{
name: 'ipad',
grep: /@mobile/,
use: {
storageState: fileURLToPath(
new URL('./test-data/display_layout_with_child_layouts.json', import.meta.url)
),
browserName: 'webkit',
...devices['iPad (gen 7) landscape'] // Complete List https://github.com/microsoft/playwright/blob/main/packages/playwright-core/src/server/deviceDescriptorsSource.json
}
},
{
name: 'iphone',
grep: /@mobile/,
use: {
storageState: fileURLToPath(
new URL('./test-data/display_layout_with_child_layouts.json', import.meta.url)
),
browserName: 'webkit',
...devices['iPhone 14 Pro'] // Complete List https://github.com/microsoft/playwright/blob/main/packages/playwright-core/src/server/deviceDescriptorsSource.json
}
}
],
reporter: [

View File

@ -363,7 +363,7 @@ test.describe('Example Imagery in Display Layout', () => {
await page.locator('li[title="View Large"]').click();
await expect(pausePlayButton).toHaveClass(/is-paused/);
await page.locator('[aria-label="Close"]').click();
await page.getByRole('button', { name: 'Close' }).click();
await expect.soft(pausePlayButton).not.toHaveClass(/is-paused/);
});
@ -386,7 +386,7 @@ test.describe('Example Imagery in Display Layout', () => {
await page.locator('li[title="View Large"]').click();
await expect(pausePlayButton).toHaveClass(/is-paused/);
await page.locator('[aria-label="Close"]').click();
await page.getByRole('button', { name: 'Close' }).click();
await expect.soft(pausePlayButton).toHaveClass(/is-paused/);
});
@ -509,7 +509,7 @@ test.describe('Example Imagery in Flexible layout', () => {
await page.getByRole('button', { name: 'Background Image', state: 'visible' });
// Close the large view
await page.getByLabel('Close').click();
await page.getByRole('button', { name: 'Close' }).click();
});
test.beforeEach(async ({ page }) => {

View File

@ -68,7 +68,7 @@ test.describe('Snapshot image tests', () => {
// expect large image to be displayed
await expect(page.getByRole('dialog').getByText('favicon-96x96.png')).toBeVisible();
await page.getByLabel('Close').click();
await page.getByRole('button', { name: 'Close' }).click();
// drop another image onto the entry
await page.dispatchEvent('.c-snapshots', 'drop', { dataTransfer: dropTransfer });

View File

@ -57,7 +57,7 @@ test.describe('Plots work in Previews', () => {
await page.getByLabel('Sine', { exact: true }).click({ button: 'right' });
await page.getByLabel('View Historical Data').click();
await expect(page.getByLabel('Preview Container').getByLabel('Plot Canvas')).toBeVisible();
await page.getByLabel('Close').click();
await page.getByRole('button', { name: 'Close' }).click();
await page.getByLabel('Expand Test Display Layout layout').click();
// change to a plot and ensure embiggen works
@ -73,7 +73,7 @@ test.describe('Plots work in Previews', () => {
await expect(page.getByLabel('Preview Container')).toBeHidden();
await page.getByLabel('Large View').click();
await expect(page.getByLabel('Preview Container').getByLabel('Plot Canvas')).toBeVisible();
await page.getByLabel('Close').click();
await page.getByRole('button', { name: 'Close' }).click();
// get last sinewave tree item (in the display layout)
await page
@ -83,6 +83,6 @@ test.describe('Plots work in Previews', () => {
.click({ button: 'right' });
await page.getByLabel('View', { exact: true }).click();
await expect(page.getByLabel('Preview Container').getByLabel('Plot Canvas')).toBeVisible();
await page.getByLabel('Close').click();
await page.getByRole('button', { name: 'Close' }).click();
});
});

View File

@ -53,7 +53,7 @@ test.describe('Preview mode', () => {
await expect(page.getByLabel('Export Table Data')).toBeVisible();
await expect(page.getByLabel('Export Marked Rows')).toBeVisible();
await page.getByRole('menuitem', { name: 'Pause' }).click();
await page.getByLabel('Close').click();
await page.getByRole('button', { name: 'Close' }).click();
await expandEntireTree(page);

View File

@ -48,7 +48,7 @@ test('Verify that the create button appears and that the Folder Domain Object is
await expect(page.locator(':nth-match(:text("Folder"), 2)')).toBeEnabled();
});
test('Verify that My Items Tree appears @ipad', async ({ page, openmctConfig }) => {
test('Verify that My Items Tree appears', async ({ page, openmctConfig }) => {
const { myItemsFolderName } = openmctConfig;
//Go to baseURL
await page.goto('./');

View File

@ -0,0 +1,59 @@
/*****************************************************************************
* Open MCT, Copyright (c) 2014-2024, 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 can quickly verify that any openmct installation is
operable and that any type of testing can proceed.
Ideally, smoke tests should make zero assumptions about how and where they are run. This makes them
more resilient to change and therefor a better indicator of failure. Smoke tests will also run quickly
as they cover a very "thin surface" of functionality.
When deciding between authoring new smoke tests or functional tests, ask yourself "would I feel
comfortable running this test during a live mission?" Avoid creating or deleting Domain Objects.
Make no assumptions about the order that elements appear in the DOM.
*/
import { expect, test } from '../../pluginFixtures.js';
test('Verify that My Items Tree appears @mobile', async ({ page, openmctConfig }) => {
const { myItemsFolderName } = openmctConfig;
//Go to baseURL
await page.goto('./');
//My Items to be visible
await expect(page.getByRole('treeitem', { name: `${myItemsFolderName}` })).toBeVisible();
});
test('Verify that user can search @mobile', async ({ page }) => {
//For now, this test is going to be hardcoded against './test-data/display_layout_with_child_layouts.json'
await page.goto('./');
await page.getByRole('searchbox', { name: 'Search Input' }).click();
await page.getByRole('searchbox', { name: 'Search Input' }).fill('Parent Display Layout');
//Search Results appear in search modal
await expect(page.getByLabel('Object Results').getByText('Parent Display Layout')).toBeVisible();
//Clicking on the search result takes you to the object
await page.getByLabel('Object Results').getByText('Parent Display Layout').click();
await page.getByTitle('Collapse Browse Pane').click();
await expect(page.getByRole('main').getByText('Parent Display Layout')).toBeVisible();
});

View File

@ -107,6 +107,7 @@
"test:debug": "KARMA_DEBUG=true karma start karma.conf.cjs",
"test:e2e": "npx playwright test",
"test:e2e:a11y": "npx playwright test --config=e2e/playwright-visual-a11y.config.js --project=chrome --grep @a11y",
"test:e2e:mobile": "npx playwright test --config=e2e/playwright-mobile.config.js",
"test:e2e:couchdb": "npx playwright test --config=e2e/playwright-ci.config.js --project=chrome --grep @couchdb --workers=1",
"test:e2e:stable": "npx playwright test --config=e2e/playwright-ci.config.js --project=chrome --grep-invert \"@unstable|@couchdb|@generatedata\"",
"test:e2e:unstable": "npx playwright test --config=e2e/playwright-ci.config.js --project=chrome --grep @unstable",

View File

@ -12,6 +12,9 @@
&:before {
// Mag glass icon
content: $glyph-icon-magnify;
body.mobile & { // Make search icon stand out in mobile
opacity: 1;
}
}
&__use-regex {
@ -47,15 +50,26 @@
display: none;
order: 99;
padding: 1px 0;
body.mobile & {
display: block;
}
}
&.is-active {
body.mobile & { // In mobile, persist the expanded search bar instead of collapsing upon clicking away
background-color: rgba($colorHeadFg, 0.2) !important;
width: 50vw !important;
}
.c-search__use-regex {
margin-left: 0;
}
&:before {
width: 0;
body.mobile & {
width: auto;
}
}
input[type='text'],

View File

@ -171,7 +171,7 @@
}
}
&__head,
//&__head,
&__pane-inspector {
body.mobile & {
display: none;
@ -189,6 +189,18 @@
margin-bottom: $interiorMargin; // Needs some additional visual separation
}
&__head {
body.mobile & {
.c-create-button,
.c-create-menu,
.c-indicator,
.c-icon-button {
// Making status area visible, but hiding some widgets for mobile.
display: none;
}
}
}
body.mobile & .l-shell__main-view-browse-bar {
margin-left: $mobileMenuIconD; // Make room for the hamburger!
.c-button[class*='__actions__edit'] {
@ -241,7 +253,20 @@
border-left: $brdr;
align-items: start;
$p: $interiorMarginSm;
padding-left: $p; padding-right: $p;
padding-left: $p;
padding-right: $p;
}
&__create-button,
&__app-logo {
flex: 0 0 auto;
}
&__create-button {
body.mobile & {
// Fixes weird gap in mobile status area
margin-right: 0px;
}
}
&__indicators {
@ -254,7 +279,6 @@
height: 24px;
overflow: hidden;
}
}
/******************************* MAIN AREA */
@ -355,14 +379,18 @@
.is-editing {
.l-shell__main-container {
$m: 3px;
box-shadow: $colorBodyBg 0 0 0 1px, $editUIAreaShdw;
box-shadow:
$colorBodyBg 0 0 0 1px,
$editUIAreaShdw;
margin-left: $m;
margin-right: $m;
top: $shellToolBarH + $shellMainBrowseBarH + $interiorMarginLg !important;
&[s-selected] {
// Provide a clearer selection context articulation for the main edit area
box-shadow: $colorBodyBg 0 0 0 1px, $editUIAreaShdwSelected;
box-shadow:
$colorBodyBg 0 0 0 1px,
$editUIAreaShdwSelected;
}
}
}

View File

@ -27,6 +27,11 @@
aria-label="Search Results Dropdown"
class="c-gsearch__dropdown"
>
<button
aria-label="Close"
class="c-gsearch__results-close c-click-icon c-overlay__close-button icon-x"
@click="selectedResult"
></button>
<div class="c-gsearch__results" :class="{ 'search-finished': !searchLoading }">
<div
v-if="objectResults?.length"

View File

@ -28,6 +28,19 @@
background: rgba($colorHeadFg, 0.2);
box-shadow: none;
flex: 1 1 auto;
body.mobile & {
@include phonePortrait() {
// This logic of expanding the search input upon click only happens in mobile portrait mode
background: $colorHeadBg;
width: 15%;
&:hover {
// When clicked, expand the search bar
background-color: rgba($colorHeadFg, 0.2);
width: 50vw;
transition: width 120ms;
}
}
}
}
}
@ -40,6 +53,17 @@
max-height: 500px;
top: $formInputH;
z-index: 60;
body.mobile & {
// Makes search in mobile look less like an overlay, and more fullscreen.
position: absolute;
top: 25px;
left: -12px;
height: calc(100vh - 22px);
min-width: 100vw;
max-height: none;
border-radius: 0px;
box-shadow: none;
}
}
&__results,
@ -55,6 +79,10 @@
> * + * {
margin-top: $interiorMarginLg;
}
body.mobile & {
// Add a margin to results so we have room for the close button
width: 93%;
}
}
&__results-section {
@ -65,6 +93,7 @@
&__results-section-title {
@include propertiesHeader();
width: 95%;
}
&__result-pane-msg {
@ -72,6 +101,22 @@
margin-top: $interiorMargin;
}
}
&__results-close {
// Close button that appears for mobile only
display: none;
body.mobile & {
display: block;
}
}
body.mobile & {
width: 50vw;
@include phonePortrait() {
// This logic only appears for a mobile portrait mode
width: 100%;
}
}
}
.c-gsearch-result {
@ -113,9 +158,9 @@
}
.c-location {
//color: $colorBodyFg;
//font-size: 0.9em;
//opacity: 0.8;
color: $colorBodyFg;
font-size: 0.9em;
opacity: 0.8;
}
}