From: Tiago Melo Date: Fri, 13 Mar 2020 01:00:51 +0000 (-0100) Subject: mgr/dashboard: Replace Protractor with Cypress X-Git-Tag: v16.1.0~2486^2~1 X-Git-Url: http://git-server-git.apps.pok.os.sepia.ceph.com/?a=commitdiff_plain;h=4af6c855c0cedbeeabe0c882773a2c891fc98145;p=ceph.git mgr/dashboard: Replace Protractor with Cypress Fixes: https://tracker.ceph.com/issues/44812 Signed-off-by: Tiago Melo --- diff --git a/src/pybind/mgr/dashboard/CMakeLists.txt b/src/pybind/mgr/dashboard/CMakeLists.txt index 7d871f986db3..104b2a1846f5 100644 --- a/src/pybind/mgr/dashboard/CMakeLists.txt +++ b/src/pybind/mgr/dashboard/CMakeLists.txt @@ -49,7 +49,7 @@ endif() add_npm_command( OUTPUT "${CMAKE_SOURCE_DIR}/src/pybind/mgr/dashboard/frontend/node_modules" - COMMAND NG_CLI_ANALYTICS=false npm ci + COMMAND NG_CLI_ANALYTICS=false CYPRESS_CACHE_FOLDER=${CMAKE_SOURCE_DIR}/build/src/pybind/mgr/dashboard/cypress npm ci DEPENDS frontend/package.json WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}/src/pybind/mgr/dashboard/frontend COMMENT "dashboard frontend dependencies are being installed" diff --git a/src/pybind/mgr/dashboard/HACKING.rst b/src/pybind/mgr/dashboard/HACKING.rst index e1963e93e105..6d0f47fab137 100644 --- a/src/pybind/mgr/dashboard/HACKING.rst +++ b/src/pybind/mgr/dashboard/HACKING.rst @@ -333,30 +333,47 @@ There are a few ways how you can try to resolve this: Running End-to-End (E2E) Tests ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -We use `Protractor `__ to run our frontend E2E -tests. +We use `Cypress `__ to run our frontend E2E tests. -Our ``run-frontend-e2e-tests.sh`` script will check if Chrome or Docker is -installed and run the tests if either is found. +E2E Prerequisites +................. + +You need to previously build the frontend. + +In some environments, depending on your user permissions and the CYPRESS_CACHE_FOLDER, +you might need to run ``npm ci`` with the ``--unsafe-perm`` flag. + +You might need to install additional packages to be able to run Cypress. +Please run ``npx cypress verify`` to verify it. + +run-frontend-e2e-tests.sh +......................... + +Our ``run-frontend-e2e-tests.sh`` script is the go to solution when you wish to +do a full scale e2e run. +It will verify if everything needed is installed, start a new vstart cluster +and run the full test suite. Start all frontend E2E tests by running:: $ ./run-frontend-e2e-tests.sh Report: - After running the tests you can find the corresponding report as well as screenshots - of failed test cases by opening the following file in your browser: + You can follow the e2e report on the terminal and you can find the screenshots + of failed test cases by opening the following directory:: - src/pybind/mgr/dashboard/frontend/.protractor-report/index.html + src/pybind/mgr/dashboard/frontend/cypress/screenshots/ Device: You can force the script to use a specific device with the ``-d`` flag:: - $ ./run-frontend-e2e-tests.sh -d + $ ./run-frontend-e2e-tests.sh -d Remote: + By default this script will stop and start a new vstart cluster. If you want to run the tests outside the ceph environment, you will need to - manually define the dashboard url using ``-r`` and, optionally, credentials (``-u``, ``-p``):: + manually define the dashboard url using ``-r`` and, optionally, credentials + (``-u``, ``-p``):: $ ./run-frontend-e2e-tests.sh -r -u -p @@ -364,29 +381,56 @@ Note: When using docker, as your device, you might need to run the script with sudo permissions. -When developing E2E tests, it is not necessary to compile the frontend code -on each change of the test files. When your development environment is -running (``npm start``), you can point Protractor to just use this -environment. To attach `Protractor `__ to -this process, run ``npm run e2e:ci``. +Other running options +..................... -Note:: +During active development, it is not recommended to run the previous script, +as it is not prepared for constant file changes. +Instead you should use one of the following commands: + +- ``npm run e2e`` - This will run ``ng serve`` and open the Cypress Test Runner. +- ``npm run e2e:ci`` - This will run ``ng serve`` and run the Cypress Test Runner once. +- ``npx cypress run`` - This calls cypress directly and will run the Cypress Test Runner. + You need to have a running frontend server. +- ``npx cypress open`` - This calls cypress directly and will open the Cypress Test Runner. + You need to have a running frontend server. + +Calling Cypress directly has the advantage that you can use any of the available +`flags `__ +to customize your test run and you don't need to start a frontend server each time. + +Using one of the ``open`` commands, will open a cypress application where you +can see all the test files you have and run each individually. +This is going to be run in watch mode, so if you make any changes to test files, +it will retrigger the test run. +This cannot be used inside docker, as it requires X11 environment to be able to open. + +By default Cypress will look for the web page at ``https://localhost:4200/``. +If you are serving it in a different URL you will need to configure it by +exporting the environment variable CYPRESS_BASE_URL with the new value. +E.g.: ``CYPRESS_BASE_URL=https://localhost:41076/ npx cypress open`` + +CYPRESS_CACHE_FOLDER +..................... - In case you have a somewhat particular environment, you might need to adapt - `protractor.conf.js` to point to the appropriate destination. +When installing cypress via npm, a binary of the cypress app will also be +downloaded and stored in a cache folder. +This removes the need to download it every time you run ``npm ci`` or even when +using cypress in a separate project. -Writing End-to-End Tests -~~~~~~~~~~~~~~~~~~~~~~~~ +By default Cypress uses ~/.cache to store the binary. +To prevent changes to the user home directory, we have changed this folder to +``/ceph/build/src/pybind/mgr/dashboard/cypress``, so when you build ceph or run +``run-frontend-e2e-tests.sh`` this is the directory Cypress will use. -To be used methods -.................. +When using any other command to install or run cypress, +it will go back to the default directory. It is recommended that you export the +CYPRESS_CACHE_FOLDER environment variable with a fixed directory, so you always +use the same directory no matter which command you use. -For clicking checkboxes, the ``clickCheckbox`` method is supposed to be used. -Due an adaption of the ```` tag, the original checkbox -is hidden and unclickable. Instead, a fancier replacement is shown. When the -developer tries to use `ElementFinder::click()` on such a checkbox, it will -raise an error. The ``clickCheckbox`` method prevents that by clicking the -label of the checkbox, like a regular user would do. + +Writing End-to-End Tests +~~~~~~~~~~~~~~~~~~~~~~~~ The PagerHelper class ..................... @@ -396,9 +440,11 @@ can be used on various pages or suites. Examples are -- ``getTableCellByContent()`` - returns a table cell by its content +- ``navigateTo()`` - Navigates to a specific page and waits for it to load +- ``getFirstTableCell()`` - returns the first table cell. You can also pass a + string with the desired content and it will return the first cell that + contains it. - ``getTabsCount()`` - returns the amount of tabs -- ``clickCheckbox()`` - clicks a checkbox Every method that could be useful on several pages belongs there. Also, methods which enhance the derived classes of the PageHelper belong there. A good @@ -418,113 +464,102 @@ talking about the pool suite, such methods would be ``create()``, ``exist()`` and ``delete()``. These methods are specific to a pool but are useful for other suites. -Methods that return HTML elements (for instance of type ``ElementFinder`` or -``ElementArrayFinder``, but also ``Promise``) which can only -be found on a specific page, should be either implemented in the helper -methods of the subclass of PageHelper or as own methods of the subclass of -PageHelper. - -Registering a new PageHelper -"""""""""""""""""""""""""""" - -If you have to create a new Helper class derived from the ``PageHelper``, -please also ensure that it is instantiated in the constructor of the -``Helper`` class. That way it can automatically be used by all other suites. - -.. code:: TypeScript - - class Helper { - // ... - pools: PoolPageHelper; - - constructor() { - this.pools = new PoolPageHelper(); - } - - // ... - } +Methods that return HTML elements which can only be found on a specific page, +should be either implemented in the helper methods of the subclass of PageHelper +or as own methods of the subclass of PageHelper. Using PageHelpers """"""""""""""""" -In any suite, an instance of the ``Helper`` class should be used to call -various ``PageHelper`` objects and their methods. This makes all methods of all -PageHelpers available to all suites. +In any suite, an instance of the specific ``Helper`` class should be +instantiated and called directly. .. code:: TypeScript + const pools = new PoolPageHelper(); + it('should create a pool', () => { - helper.pools.exist(poolName, false).then(() => { - helper.pools.navigateTo('create'); - helper.pools.create(poolName).then(() => { - helper.pools.navigateTo(); - helper.pools.exist(poolName, true); - }); - }); + pools.exist(poolName, false); + pools.navigateTo('create'); + pools.create(poolName, 8); + pools.exist(poolName, true); }); Code Style .......... -Please refer to the official `Protractor style-guide -`__ for a better insight on how -to write and structure tests as well as what exactly should be covered by -end-to-end tests. +Please refer to the official `Cypress Core Concepts +`__ +for a better insight on how to write and structure tests. ``describe()`` vs ``it()`` """""""""""""""""""""""""" -Both ``describe()`` and ``it()`` are function blocks, meaning that any executable -code necessary for the test can be contained in either block. However, Typescript -scoping rules still apply, therefore any variables declared in a ``describe`` are available -to the ``it()`` blocks inside of it. +Both ``describe()`` and ``it()`` are function blocks, meaning that any +executable code necessary for the test can be contained in either block. +However, Typescript scoping rules still apply, therefore any variables declared +in a ``describe`` are available to the ``it()`` blocks inside of it. -``describe()`` typically are containers for tests, allowing you to break tests into -multiple parts. Likewise, any setup that must be made before your tests are run can be -initialized within the ``describe()`` block. Here is an example: +``describe()`` typically are containers for tests, allowing you to break tests +into multiple parts. Likewise, any setup that must be made before your tests are +run can be initialized within the ``describe()`` block. Here is an example: .. code:: TypeScript describe('create, edit & delete image test', () => { const poolName = 'e2e_images_pool'; - beforeAll(() => { - pools.navigateTo('create'); // Need pool for image testing - pools.create(poolName, 8, 'rbd').then(() => { - pools.navigateTo(); - pools.exist(poolName, true); - }); + before(() => { + cy.login(); + pools.navigateTo('create'); + pools.create(poolName, 8, 'rbd'); + pools.exist(poolName, true); + }); + + beforeEach(() => { + cy.login(); images.navigateTo(); }); + //... + + }); + As shown, we can initiate the variable ``poolName`` as well as run commands -before our test suite begins (creating a pool). ``describe()`` block messages should -include what the test suite is. +before our test suite begins (creating a pool). ``describe()`` block messages +should include what the test suite is. -``it()`` blocks typically are parts of an overarching test. They contain the functionality of -the test suite, each performing individual roles. Here is an example: +``it()`` blocks typically are parts of an overarching test. They contain the +functionality of the test suite, each performing individual roles. +Here is an example: .. code:: TypeScript - describe('create, edit & delete image test', () => { - it('should create image', () => { - images.createImage(imageName, poolName, '1'); - expect(images.getTableCell(imageName).isPresent()).toBe(true); - }); - it('should edit image', () => { - images.editImage(imageName, poolName, newImageName, '2'); - expect(images.getTableCell(newImageName).isPresent()).toBe(true); + describe('create, edit & delete image test', () => { + //... + + it('should create image', () => { + images.createImage(imageName, poolName, '1'); + images.getFirstTableCell(imageName).should('exist'); + }); + + it('should edit image', () => { + images.editImage(imageName, poolName, newImageName, '2'); + images.getFirstTableCell(newImageName).should('exist'); + }); + + //... }); - //... - }); -As shown from the previous example, our ``describe()`` test suite is to create, edit -and delete an image. Therefore, each ``it()`` completes one of these steps, one for creating, -one for editing, and so on. Likewise, every ``it()`` blocks message should be in lowercase -and written so long as "it" can be the prefix of the message. For example, ``it('edits the test image' () => ...)`` -vs. ``it('image edit test' () => ...)``. As shown, the first example makes grammatical sense with ``it()`` as the -prefix whereas the second message does not.``it()`` should describe what the individual test is doing and -what it expects to happen. +As shown from the previous example, our ``describe()`` test suite is to create, +edit and delete an image. Therefore, each ``it()`` completes one of these steps, +one for creating, one for editing, and so on. Likewise, every ``it()`` blocks +message should be in lowercase and written so long as "it" can be the prefix of +the message. For example, ``it('edits the test image' () => ...)`` vs. +``it('image edit test' () => ...)``. As shown, the first example makes +grammatical sense with ``it()`` as the prefix whereas the second message does +not. ``it()`` should describe what the individual test is doing and what it +expects to happen. Differences between Frontend Unit Tests and End-to-End (E2E) Tests / FAQ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ @@ -537,10 +572,6 @@ What are E2E/unit tests designed for? E2E test: -"Protractor is an end-to-end test framework for Angular and AngularJS applications. -Protractor runs tests against your application running in a real browser, -interacting with it as a user would." `(src) `__ - It requires a fully functional system and tests the interaction of all components of the application (Ceph, back-end, front-end). E2E tests are designed to mimic the behavior of the user when interacting with the application diff --git a/src/pybind/mgr/dashboard/frontend/.gitignore b/src/pybind/mgr/dashboard/frontend/.gitignore index 08ded3253839..5bb225251f09 100644 --- a/src/pybind/mgr/dashboard/frontend/.gitignore +++ b/src/pybind/mgr/dashboard/frontend/.gitignore @@ -34,9 +34,8 @@ testem.log /src/unit-test-configuration.ts # e2e -/e2e/*.js -/e2e/*.map -.protractor-fail-fast +/cypress/screenshots +/cypress/videos # System Files .DS_Store diff --git a/src/pybind/mgr/dashboard/frontend/angular.json b/src/pybind/mgr/dashboard/frontend/angular.json index c54fb5f553dc..da61a75de20c 100644 --- a/src/pybind/mgr/dashboard/frontend/angular.json +++ b/src/pybind/mgr/dashboard/frontend/angular.json @@ -204,32 +204,6 @@ } }, "cli": {} - }, - "ceph-dashboard-e2e": { - "root": "", - "sourceRoot": "", - "projectType": "application", - "architect": { - "e2e": { - "builder": "@angular-devkit/build-angular:protractor", - "options": { - "protractorConfig": "./protractor.conf.js", - "devServerTarget": "ceph-dashboard:serve" - } - }, - "lint": { - "builder": "@angular-devkit/build-angular:tslint", - "options": { - "tsConfig": [ - "e2e/tsconfig.e2e.json" - ], - "exclude": [ - "**/node_modules/**" - ] - } - } - }, - "cli": {} } }, "defaultProject": "ceph-dashboard", diff --git a/src/pybind/mgr/dashboard/frontend/cypress.json b/src/pybind/mgr/dashboard/frontend/cypress.json new file mode 100644 index 000000000000..4f604241edb9 --- /dev/null +++ b/src/pybind/mgr/dashboard/frontend/cypress.json @@ -0,0 +1,14 @@ +{ + "baseUrl": "http://localhost:4200/", + "ignoreTestFiles": [ + "*.po.ts" + ], + "supportFile": "cypress/support/index.ts", + "video": false, + "defaultCommandTimeout": 20000, + "viewportHeight": 1080, + "viewportWidth": 1920, + "pluginsFile": false, + "fixturesFolder": false, + "projectId": "k7ab29" +} diff --git a/src/pybind/mgr/dashboard/frontend/cypress/integration/block/images.e2e-spec.ts b/src/pybind/mgr/dashboard/frontend/cypress/integration/block/images.e2e-spec.ts new file mode 100644 index 000000000000..87900a0e1a51 --- /dev/null +++ b/src/pybind/mgr/dashboard/frontend/cypress/integration/block/images.e2e-spec.ts @@ -0,0 +1,92 @@ +import { PoolPageHelper } from '../pools/pools.po'; +import { ImagesPageHelper } from './images.po'; + +describe('Images page', () => { + const pools = new PoolPageHelper(); + const images = new ImagesPageHelper(); + + const poolName = 'e2e_images_pool'; + + before(() => { + cy.login(); + // Need pool for image testing + pools.navigateTo('create'); + pools.create(poolName, 8, 'rbd'); + pools.exist(poolName, true); + }); + + after(() => { + // Deletes images test pool + pools.navigateTo(); + pools.delete(poolName); + pools.navigateTo(); + pools.exist(poolName, false); + }); + + beforeEach(() => { + cy.login(); + images.navigateTo(); + }); + + it('should open and show breadcrumb', () => { + images.expectBreadcrumbText('Images'); + }); + + it('should show four tabs', () => { + images.getTabsCount().should('eq', 4); + }); + + it('should show text for all tabs', () => { + images.getTabText(0).should('eq', 'Images'); + images.getTabText(1).should('eq', 'Namespaces'); + images.getTabText(2).should('eq', 'Trash'); + images.getTabText(3).should('eq', 'Overall Performance'); + }); + + describe('create, edit & delete image test', () => { + const imageName = 'e2e_images#image'; + const newImageName = 'e2e_images#image_new'; + + it('should create image', () => { + images.createImage(imageName, poolName, '1'); + images.getFirstTableCell(imageName).should('exist'); + }); + + it('should edit image', () => { + images.editImage(imageName, poolName, newImageName, '2'); + images.getFirstTableCell(newImageName).should('exist'); + }); + + it('should delete image', () => { + images.delete(newImageName); + }); + }); + + describe('move to trash, restore and purge image tests', () => { + const imageName = 'e2e_trash#image'; + const newImageName = 'e2e_newtrash#image'; + + before(() => { + cy.login(); + // Need image for trash testing + images.createImage(imageName, poolName, '1'); + images.getFirstTableCell(imageName).should('exist'); + }); + + it('should move the image to the trash', () => { + images.moveToTrash(imageName); + images.getFirstTableCell(imageName).should('exist'); + }); + + it('should restore image to images table', () => { + images.restoreImage(imageName, newImageName); + images.getFirstTableCell(newImageName).should('exist'); + }); + + it('should purge trash in images trash tab', () => { + images.getFirstTableCell(newImageName).should('exist'); + images.moveToTrash(newImageName); + images.purgeTrash(newImageName, poolName); + }); + }); +}); diff --git a/src/pybind/mgr/dashboard/frontend/cypress/integration/block/images.po.ts b/src/pybind/mgr/dashboard/frontend/cypress/integration/block/images.po.ts new file mode 100644 index 000000000000..f6cc1b987391 --- /dev/null +++ b/src/pybind/mgr/dashboard/frontend/cypress/integration/block/images.po.ts @@ -0,0 +1,115 @@ +import { PageHelper } from '../page-helper.po'; + +export class ImagesPageHelper extends PageHelper { + pages = { + index: { url: '#/block/rbd', id: 'cd-rbd-list' }, + create: { url: '#/block/rbd/create', id: 'cd-rbd-form' } + }; + + // Creates a block image and fills in the name, pool, and size fields. + // Then checks if the image is present in the Images table. + createImage(name: string, pool: string, size: string) { + this.navigateTo('create'); + + cy.get('#name').type(name); // Enter in image name + + // Select image pool + cy.contains('Loading...').should('not.exist'); + this.selectOption('pool', pool); + cy.get('#pool').should('have.class', 'ng-valid'); // check if selected + + // Enter in the size of the image + cy.get('#size').type(size); + + // Click the create button and wait for image to be made + cy.contains('button', 'Create RBD').click(); + this.getFirstTableCell(name).should('exist'); + } + + editImage(name: string, pool: string, newName: string, newSize: string) { + const base_url = '#/block/rbd/edit/'; + const editURL = base_url + .concat(encodeURIComponent(pool)) + .concat('%2F') + .concat(encodeURIComponent(name)); + cy.visit(editURL); + + // Wait until data is loaded + cy.get('#pool').should('contain.value', pool); + + cy.get('#name').clear().type(newName); + cy.get('#size').clear().type(newSize); // click the size box and send new size + + cy.contains('button', 'Edit RBD').click(); + + this.getExpandCollapseElement(newName).click(); + cy.get('.table.table-striped.table-bordered').contains('td', newSize); + } + + // Selects RBD image and moves it to the trash, + // checks that it is present in the trash table + moveToTrash(name: string) { + // wait for image to be created + cy.get('.datatable-body').first().should('not.contain.text', '(Creating...)'); + + this.getFirstTableCell(name).click(); + + // click on the drop down and selects the move to trash option + cy.get('.table-actions button.dropdown-toggle').first().click(); + cy.get('li.move-to-trash').click(); + + cy.contains('button', 'Move Image').should('be.visible').click(); + + // Clicks trash tab + cy.contains('.nav-link', 'Trash').click(); + this.getFirstTableCell(name).should('exist'); + } + + // Checks trash tab table for image and then restores it to the RBD Images table + // (could change name if new name is given) + restoreImage(name: string, newName?: string) { + // clicks on trash tab + cy.contains('.nav-link', 'Trash').click(); + + // wait for table to load + this.getFirstTableCell(name).click(); + cy.contains('button', 'Restore').click(); + + // wait for pop-up to be visible (checks for title of pop-up) + cy.get('#name').should('be.visible'); + + // If a new name for the image is passed, it changes the name of the image + if (newName !== undefined) { + // click name box and send new name + cy.get('#name').clear().type(newName); + } + + cy.contains('button', 'Restore Image').click(); + + // clicks images tab + cy.contains('.nav-link', 'Images').click(); + + this.getFirstTableCell(newName).should('exist'); + } + + // Enters trash tab and purges trash, thus emptying the trash table. + // Checks if Image is still in the table. + purgeTrash(name: string, pool?: string) { + // clicks trash tab + cy.contains('.nav-link', 'Trash').click(); + cy.contains('button', 'Purge Trash').click(); + + // Check for visibility of modal container + cy.get('.modal-header').should('be.visible'); + + // If purgeing a specific pool, selects that pool if given + if (pool !== undefined) { + this.selectOption('poolName', pool); + cy.get('#poolName').should('have.class', 'ng-valid'); // check if pool is selected + } + cy.get('#purgeFormButton').click(); + // Wait for image to delete and check it is not present + + this.getFirstTableCell(name).should('not.exist'); + } +} diff --git a/src/pybind/mgr/dashboard/frontend/cypress/integration/block/iscsi.e2e-spec.ts b/src/pybind/mgr/dashboard/frontend/cypress/integration/block/iscsi.e2e-spec.ts new file mode 100644 index 000000000000..f7154fb59e02 --- /dev/null +++ b/src/pybind/mgr/dashboard/frontend/cypress/integration/block/iscsi.e2e-spec.ts @@ -0,0 +1,24 @@ +import { IscsiPageHelper } from './iscsi.po'; + +describe('Iscsi Page', () => { + const iscsi = new IscsiPageHelper(); + + beforeEach(() => { + cy.login(); + iscsi.navigateTo(); + }); + + it('should open and show breadcrumb', () => { + iscsi.expectBreadcrumbText('Overview'); + }); + + it('should check that tables are displayed and legends are correct', () => { + // Check tables are displayed + iscsi.getDataTables().its(0).should('be.visible'); + iscsi.getDataTables().its(1).should('visible'); + + // Check that legends are correct + iscsi.getLegends().its(0).should('contain.text', 'Gateways'); + iscsi.getLegends().its(1).should('contain.text', 'Images'); + }); +}); diff --git a/src/pybind/mgr/dashboard/frontend/cypress/integration/block/iscsi.po.ts b/src/pybind/mgr/dashboard/frontend/cypress/integration/block/iscsi.po.ts new file mode 100644 index 000000000000..08efa6408bd7 --- /dev/null +++ b/src/pybind/mgr/dashboard/frontend/cypress/integration/block/iscsi.po.ts @@ -0,0 +1,7 @@ +import { PageHelper } from '../page-helper.po'; + +export class IscsiPageHelper extends PageHelper { + pages = { + index: { url: '#/block/iscsi/overview', id: 'cd-iscsi' } + }; +} diff --git a/src/pybind/mgr/dashboard/frontend/cypress/integration/block/mirroring.e2e-spec.ts b/src/pybind/mgr/dashboard/frontend/cypress/integration/block/mirroring.e2e-spec.ts new file mode 100644 index 000000000000..c7bd42782389 --- /dev/null +++ b/src/pybind/mgr/dashboard/frontend/cypress/integration/block/mirroring.e2e-spec.ts @@ -0,0 +1,53 @@ +import { PoolPageHelper } from '../pools/pools.po'; +import { MirroringPageHelper } from './mirroring.po'; + +describe('Mirroring page', () => { + const pools = new PoolPageHelper(); + const mirroring = new MirroringPageHelper(); + + beforeEach(() => { + cy.login(); + mirroring.navigateTo(); + }); + + it('should open and show breadcrumb', () => { + mirroring.expectBreadcrumbText('Mirroring'); + }); + + it('should show three tabs', () => { + mirroring.getTabsCount().should('eq', 3); + }); + + it('should show text for all tabs', () => { + mirroring.getTabText(0).should('eq', 'Issues'); + mirroring.getTabText(1).should('eq', 'Syncing'); + mirroring.getTabText(2).should('eq', 'Ready'); + }); + + describe('checks that edit mode functionality shows in the pools table', () => { + const poolName = 'mirroring_test'; + + beforeEach(() => { + pools.navigateTo('create'); // Need pool for mirroring testing + pools.create(poolName, 8, 'rbd'); + pools.navigateTo(); + pools.exist(poolName, true); + }); + + it('tests editing mode for pools', () => { + mirroring.navigateTo(); + + mirroring.editMirror(poolName, 'Pool'); + mirroring.getFirstTableCell('pool').should('be.visible'); + mirroring.editMirror(poolName, 'Image'); + mirroring.getFirstTableCell('image').should('be.visible'); + mirroring.editMirror(poolName, 'Disabled'); + mirroring.getFirstTableCell('disabled').should('be.visible'); + }); + + afterEach(() => { + pools.navigateTo(); + pools.delete(poolName); + }); + }); +}); diff --git a/src/pybind/mgr/dashboard/frontend/cypress/integration/block/mirroring.po.ts b/src/pybind/mgr/dashboard/frontend/cypress/integration/block/mirroring.po.ts new file mode 100644 index 000000000000..8450763d301a --- /dev/null +++ b/src/pybind/mgr/dashboard/frontend/cypress/integration/block/mirroring.po.ts @@ -0,0 +1,32 @@ +import { PageHelper } from '../page-helper.po'; + +const pages = { + index: { url: '#/block/mirroring', id: 'cd-mirroring' } +}; + +export class MirroringPageHelper extends PageHelper { + pages = pages; + + /** + * Goes to the mirroring page and edits a pool in the Pool table. Clicks on the + * pool and chooses an option (either pool, image, or disabled) + */ + @PageHelper.restrictTo(pages.index.url) + editMirror(name: string, option: string) { + // Clicks the pool in the table + this.getFirstTableCell(name).click(); + + // Clicks the Edit Mode button + cy.contains('button', 'Edit Mode').click(); + + // Clicks the drop down in the edit pop-up, then clicks the Update button + cy.get('.modal-content').should('be.visible'); + this.selectOption('mirrorMode', option); + + // Clicks update button and checks if the mode has been changed + cy.contains('button', 'Update').click(); + cy.contains('.modal-dialog', 'Edit pool mirror mode').should('not.exist'); + const val = option.toLowerCase(); // used since entries in table are lower case + this.getFirstTableCell(val).should('be.visible'); + } +} diff --git a/src/pybind/mgr/dashboard/frontend/cypress/integration/cluster/configuration.e2e-spec.ts b/src/pybind/mgr/dashboard/frontend/cypress/integration/cluster/configuration.e2e-spec.ts new file mode 100644 index 000000000000..fda568206752 --- /dev/null +++ b/src/pybind/mgr/dashboard/frontend/cypress/integration/cluster/configuration.e2e-spec.ts @@ -0,0 +1,66 @@ +import { ConfigurationPageHelper } from './configuration.po'; + +describe('Configuration page', () => { + const configuration = new ConfigurationPageHelper(); + + beforeEach(() => { + cy.login(); + configuration.navigateTo(); + }); + + describe('breadcrumb test', () => { + it('should open and show breadcrumb', () => { + configuration.expectBreadcrumbText('Configuration'); + }); + }); + + describe('fields check', () => { + beforeEach(() => { + configuration.getExpandCollapseElement().click(); + }); + + it('should verify that selected footer increases when an entry is clicked', () => { + configuration.getTableSelectedCount().should('eq', 1); + }); + + it('should check that details table opens and tab is correct', () => { + configuration.getStatusTables().should('be.visible'); + configuration.getTabsCount().should('eq', 1); + configuration.getTabText(0).should('eq', 'Details'); + }); + }); + + describe('edit configuration test', () => { + const configName = 'client_cache_size'; + + beforeEach(() => { + configuration.clearTableSearchInput(); + }); + + after(() => { + configuration.configClear(configName); + }); + + it('should click and edit a configuration and results should appear in the table', () => { + configuration.edit( + configName, + ['global', '1'], + ['mon', '2'], + ['mgr', '3'], + ['osd', '4'], + ['mds', '5'], + ['client', '6'] + ); + }); + + it('should show only modified configurations', () => { + configuration.filterTable('Modified', 'yes'); + configuration.getTableFoundCount().should('eq', 1); + }); + + it('should hide all modified configurations', () => { + configuration.filterTable('Modified', 'no'); + configuration.getTableFoundCount().should('gt', 1); + }); + }); +}); diff --git a/src/pybind/mgr/dashboard/frontend/cypress/integration/cluster/configuration.po.ts b/src/pybind/mgr/dashboard/frontend/cypress/integration/cluster/configuration.po.ts new file mode 100644 index 000000000000..131ad2708a2a --- /dev/null +++ b/src/pybind/mgr/dashboard/frontend/cypress/integration/cluster/configuration.po.ts @@ -0,0 +1,87 @@ +import { PageHelper } from '../page-helper.po'; + +export class ConfigurationPageHelper extends PageHelper { + pages = { + index: { url: '#/configuration', id: 'cd-configuration' } + }; + + /** + * Clears out all the values in a config to reset before and after testing + * Does not work for configs with checkbox only, possible future PR + */ + configClear(name: string) { + this.navigateTo(); + const valList = ['global', 'mon', 'mgr', 'osd', 'mds', 'client']; // Editable values + + // Enter config setting name into filter box + this.seachTable(name); + + // Selects config that we want to clear + this.getFirstTableCell(name).click(); // waits for config to be clickable and click + cy.contains('button', 'Edit').click(); // clicks button to edit + + // Wait for the data to load + cy.contains('.card-header', `Edit ${name}`); + + for (const i of valList) { + cy.get(`#${i}`).clear(); + } + // Clicks save button and checks that values are not present for the selected config + cy.contains('button', 'Save').click(); + + // Enter config setting name into filter box + this.seachTable(name); + + // Expand row + this.getExpandCollapseElement(name).click(); + + // Checks for visibility of details tab + this.getStatusTables().should('be.visible'); + + for (const i of valList) { + // Waits until values are not present in the details table + this.getStatusTables().should('not.contain.text', i + ':'); + } + } + + /** + * Clicks the designated config, then inputs the values passed into the edit function. + * Then checks if the edit is reflected in the config table. + * Takes in name of config and a list of tuples of values the user wants edited, + * each tuple having the desired value along with the number tehey want for that value. + * Ex: [global, '2'] is the global value with an input of 2 + */ + edit(name: string, ...values: [string, string][]) { + + // Enter config setting name into filter box + this.seachTable(name); + + // Selects config that we want to edit + this.getFirstTableCell(name).click(); // waits for config to be clickable and click + cy.contains('button', 'Edit').click(); // clicks button to edit + + cy.contains('.card-header', `Edit ${name}`); + + values.forEach((valtuple) => { + // Finds desired value based off given list + cy.get(`#${valtuple[0]}`).type(valtuple[1]); // of values and inserts the given number for the value + }); + + // Clicks save button then waits until the desired config is visible, clicks it, + // then checks that each desired value appears with the desired number + cy.contains('button', 'Save').click(); + + // Enter config setting name into filter box + this.seachTable(name); + + // Checks for visibility of config in table + this.getExpandCollapseElement(name).should('be.visible').click(); + + // Clicks config + values.forEach((value) => { + // iterates through list of values and + // checks if the value appears in details with the correct number attatched + cy.contains('.table.table-striped.table-bordered', `${value[0]}\: ${value[1]}`); + }); + } +} diff --git a/src/pybind/mgr/dashboard/frontend/cypress/integration/cluster/crush-map.e2e-spec.ts b/src/pybind/mgr/dashboard/frontend/cypress/integration/cluster/crush-map.e2e-spec.ts new file mode 100644 index 000000000000..2c8d1322f4e9 --- /dev/null +++ b/src/pybind/mgr/dashboard/frontend/cypress/integration/cluster/crush-map.e2e-spec.ts @@ -0,0 +1,36 @@ +import { CrushMapPageHelper } from './crush-map.po'; + +describe('CRUSH map page', () => { + const crushmap = new CrushMapPageHelper(); + + beforeEach(() => { + cy.login(); + crushmap.navigateTo(); + }); + + describe('breadcrumb test', () => { + it('should open and show breadcrumb', () => { + crushmap.expectBreadcrumbText('CRUSH map'); + }); + }); + + describe('fields check', () => { + it('should check that title & table appears', () => { + // Check that title (CRUSH map viewer) appears + crushmap.getPageTitle().should('equal', 'CRUSH map viewer'); + + // Check that title appears once OSD is clicked + crushmap.getCrushNode(1).click(); + + crushmap + .getLegends() + .invoke('text') + .then((legend) => { + crushmap.getCrushNode(1).should('have.text', legend); + }); + + // Check that table appears once OSD is clicked + crushmap.getDataTables().should('be.visible'); + }); + }); +}); diff --git a/src/pybind/mgr/dashboard/frontend/cypress/integration/cluster/crush-map.po.ts b/src/pybind/mgr/dashboard/frontend/cypress/integration/cluster/crush-map.po.ts new file mode 100644 index 000000000000..a5d2d591ce04 --- /dev/null +++ b/src/pybind/mgr/dashboard/frontend/cypress/integration/cluster/crush-map.po.ts @@ -0,0 +1,13 @@ +import { PageHelper } from '../page-helper.po'; + +export class CrushMapPageHelper extends PageHelper { + pages = { index: { url: '#/crush-map', id: 'cd-crushmap' } }; + + getPageTitle() { + return cy.get('cd-crushmap .card-header').text(); + } + + getCrushNode(idx: number) { + return cy.get('.node-name.type-osd').eq(idx); + } +} diff --git a/src/pybind/mgr/dashboard/frontend/cypress/integration/cluster/hosts.e2e-spec.ts b/src/pybind/mgr/dashboard/frontend/cypress/integration/cluster/hosts.e2e-spec.ts new file mode 100644 index 000000000000..045b18f60cdb --- /dev/null +++ b/src/pybind/mgr/dashboard/frontend/cypress/integration/cluster/hosts.e2e-spec.ts @@ -0,0 +1,38 @@ +import { HostsPageHelper } from './hosts.po'; + +describe('Hosts page', () => { + const hosts = new HostsPageHelper(); + + beforeEach(() => { + cy.login(); + hosts.navigateTo(); + }); + + describe('breadcrumb and tab tests', () => { + it('should open and show breadcrumb', () => { + hosts.expectBreadcrumbText('Hosts'); + }); + + it('should show two tabs', () => { + hosts.getTabsCount().should('eq', 2); + }); + + it('should show hosts list tab at first', () => { + hosts.getTabText(0).should('eq', 'Hosts List'); + }); + + it('should show overall performance as a second tab', () => { + hosts.getTabText(1).should('eq', 'Overall Performance'); + }); + }); + + describe('services link test', () => { + it('should check at least one host is present', () => { + hosts.check_for_host(); + }); + + it('should check services link(s) work for first host', () => { + hosts.check_services_links(); + }); + }); +}); diff --git a/src/pybind/mgr/dashboard/frontend/cypress/integration/cluster/hosts.po.ts b/src/pybind/mgr/dashboard/frontend/cypress/integration/cluster/hosts.po.ts new file mode 100644 index 000000000000..d3e75400bba5 --- /dev/null +++ b/src/pybind/mgr/dashboard/frontend/cypress/integration/cluster/hosts.po.ts @@ -0,0 +1,31 @@ +import { PageHelper } from '../page-helper.po'; + +export class HostsPageHelper extends PageHelper { + pages = { index: { url: '#/hosts', id: 'cd-hosts' } }; + + check_for_host() { + this.getTableTotalCount().should('not.be.eq', 0); + } + + // function that checks all services links work for first + // host in table + check_services_links() { + // check that text (links) is present in services box + let links_tested = 0; + + cy.get('cd-hosts a.service-link') + .should('have.length.greaterThan', 0) + .then(($elems) => { + $elems.each((_i, $el) => { + // click link, check it worked by looking for changed breadcrumb, + // navigate back to hosts page, repeat until all links checked + cy.contains('a', $el.innerText).should('exist').click(); + this.expectBreadcrumbText('Performance Counters'); + this.navigateTo(); + links_tested++; + }); + // check if any links were actually tested + expect(links_tested).gt(0); + }); + } +} diff --git a/src/pybind/mgr/dashboard/frontend/cypress/integration/cluster/logs.e2e-spec.ts b/src/pybind/mgr/dashboard/frontend/cypress/integration/cluster/logs.e2e-spec.ts new file mode 100644 index 000000000000..0e918fbf8c2a --- /dev/null +++ b/src/pybind/mgr/dashboard/frontend/cypress/integration/cluster/logs.e2e-spec.ts @@ -0,0 +1,73 @@ +import { PoolPageHelper } from '../pools/pools.po'; +import { ConfigurationPageHelper } from './configuration.po'; +import { LogsPageHelper } from './logs.po'; + +describe('Logs page', () => { + const logs = new LogsPageHelper(); + const pools = new PoolPageHelper(); + const configuration = new ConfigurationPageHelper(); + + const poolname = 'e2e_logs_test_pool'; + const configname = 'log_graylog_port'; + const today = new Date(); + let hour = today.getHours(); + if (hour > 12) { + hour = hour - 12; + } + const minute = today.getMinutes(); + + beforeEach(() => { + cy.login(); + }); + + describe('breadcrumb and tab tests', () => { + beforeEach(() => { + logs.navigateTo(); + }); + + it('should open and show breadcrumb', () => { + logs.expectBreadcrumbText('Logs'); + }); + + it('should show two tabs', () => { + logs.getTabsCount().should('eq', 2); + }); + + it('should show cluster logs tab at first', () => { + logs.getTabText(0).should('eq', 'Cluster Logs'); + }); + + it('should show audit logs as a second tab', () => { + logs.getTabText(1).should('eq', 'Audit Logs'); + }); + }); + + describe('audit logs respond to pool creation and deletion test', () => { + it('should create pool and check audit logs reacted', () => { + pools.navigateTo('create'); + pools.create(poolname, 8); + pools.navigateTo(); + pools.exist(poolname, true); + logs.checkAuditForPoolFunction(poolname, 'create', hour, minute); + }); + + it('should delete pool and check audit logs reacted', () => { + pools.navigateTo(); + pools.delete(poolname); + logs.checkAuditForPoolFunction(poolname, 'delete', hour, minute); + }); + }); + + describe('audit logs respond to editing configuration setting test', () => { + it('should change config settings and check audit logs reacted', () => { + configuration.navigateTo(); + configuration.edit(configname, ['global', '5']); + + logs.navigateTo(); + logs.checkAuditForConfigChange(configname, 'global', hour, minute); + + configuration.navigateTo(); + configuration.configClear(configname); + }); + }); +}); diff --git a/src/pybind/mgr/dashboard/frontend/cypress/integration/cluster/logs.po.ts b/src/pybind/mgr/dashboard/frontend/cypress/integration/cluster/logs.po.ts new file mode 100644 index 000000000000..bf5776ceb29b --- /dev/null +++ b/src/pybind/mgr/dashboard/frontend/cypress/integration/cluster/logs.po.ts @@ -0,0 +1,70 @@ +import { PageHelper } from '../page-helper.po'; + +export class LogsPageHelper extends PageHelper { + pages = { + index: { url: '#/logs', id: 'cd-logs' } + }; + + checkAuditForPoolFunction(poolname: string, poolfunction: string, hour: number, minute: number) { + this.navigateTo(); + + // sometimes the modal from deleting pool is still present at this point. + // This wait makes sure it isn't + cy.contains('.modal-dialog', 'Delete Pool').should('not.exist'); + + // go to audit logs tab + cy.contains('.nav-link', 'Audit Logs').click(); + + // Enter an earliest time so that no old messages with the same pool name show up + cy.get('.bs-timepicker-field').its(0).clear(); + + if (hour < 10) { + cy.get('.bs-timepicker-field').its(0).type('0'); + } + cy.get('.bs-timepicker-field').its(0).type(`${hour}`); + + cy.get('.bs-timepicker-field').its(1).clear(); + if (minute < 10) { + cy.get('.bs-timepicker-field').its(1).type('0'); + } + cy.get('.bs-timepicker-field').its(1).type(`${minute}`); + + // Enter the pool name into the filter box + cy.get('input.form-control.ng-valid').first().clear().type(poolname); + + cy.get('.tab-pane.active') + .get('.card-body') + .get('.message') + .should('contain.text', poolname) + .and('contain.text', `pool ${poolfunction}`); + } + + checkAuditForConfigChange(configname: string, setting: string, hour: number, minute: number) { + this.navigateTo(); + + // go to audit logs tab + cy.contains('.nav-link', 'Audit Logs').click(); + + // Enter an earliest time so that no old messages with the same config name show up + cy.get('.bs-timepicker-field').its(0).clear(); + if (hour < 10) { + cy.get('.bs-timepicker-field').its(0).type('0'); + } + cy.get('.bs-timepicker-field').its(0).type(`${hour}`); + + cy.get('.bs-timepicker-field').its(1).clear(); + if (minute < 10) { + cy.get('.bs-timepicker-field').its(1).type('0'); + } + cy.get('.bs-timepicker-field').its(1).type(`${minute}`); + + // Enter the config name into the filter box + cy.get('input.form-control.ng-valid').first().clear().type(configname); + + cy.get('.tab-pane.active') + .get('.card-body') + .get('.message') + .should('contain.text', configname) + .and('contain.text', setting); + } +} diff --git a/src/pybind/mgr/dashboard/frontend/cypress/integration/cluster/mgr-modules.e2e-spec.ts b/src/pybind/mgr/dashboard/frontend/cypress/integration/cluster/mgr-modules.e2e-spec.ts new file mode 100644 index 000000000000..18a1c5db0ce0 --- /dev/null +++ b/src/pybind/mgr/dashboard/frontend/cypress/integration/cluster/mgr-modules.e2e-spec.ts @@ -0,0 +1,43 @@ +import { ManagerModulesPageHelper } from './mgr-modules.po'; + +describe('Manager modules page', () => { + const mgrmodules = new ManagerModulesPageHelper(); + + beforeEach(() => { + cy.login(); + mgrmodules.navigateTo(); + }); + + describe('breadcrumb test', () => { + it('should open and show breadcrumb', () => { + mgrmodules.expectBreadcrumbText('Manager modules'); + }); + }); + + describe('verifies editing functionality for manager modules', () => { + it('should test editing on diskprediction_local module', () => { + const diskpredLocalArr = [ + ['11', 'predict_interval'], + ['0122', 'sleep_interval'] + ]; + mgrmodules.editMgrModule('diskprediction_local', diskpredLocalArr); + }); + + it('should test editing on balancer module', () => { + const balancerArr = [['rq', 'pool_ids']]; + mgrmodules.editMgrModule('balancer', balancerArr); + }); + + it('should test editing on dashboard module', () => { + const dashboardArr = [ + ['rq', 'RGW_API_USER_ID'], + ['rafa', 'GRAFANA_API_PASSWORD'] + ]; + mgrmodules.editMgrModule('dashboard', dashboardArr); + }); + + it('should test editing on devicehealth module', () => { + mgrmodules.editDevicehealth('1987', 'sox', '1999', '2020', '456', '567'); + }); + }); +}); diff --git a/src/pybind/mgr/dashboard/frontend/cypress/integration/cluster/mgr-modules.po.ts b/src/pybind/mgr/dashboard/frontend/cypress/integration/cluster/mgr-modules.po.ts new file mode 100644 index 000000000000..eaf93f9465fe --- /dev/null +++ b/src/pybind/mgr/dashboard/frontend/cypress/integration/cluster/mgr-modules.po.ts @@ -0,0 +1,120 @@ +import { PageHelper } from '../page-helper.po'; + +export class ManagerModulesPageHelper extends PageHelper { + pages = { index: { url: '#/mgr-modules', id: 'cd-mgr-module-list' } }; + + /** + * Selects the Manager Module and then fills in the desired fields. + * Doesn't check/uncheck boxes because it is not reflected in the details table. + * DOES NOT WORK FOR ALL MGR MODULES, for example, Device health + */ + editMgrModule(name: string, tuple: string[][]) { + this.getFirstTableCell(name).click(); + cy.contains('button', 'Edit').click(); + + for (const entry of tuple) { + // Clears fields and adds edits + cy.get(`#${entry[1]}`).clear().type(entry[0]); + } + + cy.contains('button', 'Update').click(); + // Checks if edits appear + this.getExpandCollapseElement(name).should('be.visible').click(); + for (const entry of tuple) { + cy.get('.datatable-body').last().contains(entry[0]); + } + + // Clear mgr module of all edits made to it + this.getFirstTableCell(name).click(); + cy.contains('button', 'Edit').click(); + + // Clears the editable fields + for (const entry of tuple) { + cy.get(`#${entry[1]}`).clear(); + } + + // Checks that clearing represents in details tab of module + cy.contains('button', 'Update').click(); + this.getExpandCollapseElement(name).should('be.visible').click(); + for (const entry of tuple) { + cy.get('.datatable-body').eq(1).should('contain', entry[1]).and('not.contain', entry[0]); + } + } + + /** + * Selects the Devicehealth manager module, then fills in the desired fields, + * including all fields except checkboxes. + * Then checks if these edits appear in the details table. + */ + editDevicehealth( + threshhold?: string, + pooln?: string, + retention?: string, + scrape?: string, + sleep?: string, + warn?: string + ) { + let devHealthArray: [string, string][]; + devHealthArray = [ + [threshhold, 'mark_out_threshold'], + [pooln, 'pool_name'], + [retention, 'retention_period'], + [scrape, 'scrape_frequency'], + [sleep, 'sleep_interval'], + [warn, 'warn_threshold'] + ]; + + this.getFirstTableCell('devicehealth').click(); + cy.contains('button', 'Edit').click(); + for (let i = 0, devHealthTuple; (devHealthTuple = devHealthArray[i]); i++) { + if (devHealthTuple[0] !== undefined) { + // Clears and inputs edits + cy.get(`#${devHealthTuple[1]}`).type(devHealthTuple[0]); + } + } + + cy.contains('button', 'Update').click(); + this.getFirstTableCell('devicehealth').should('be.visible'); + // Checks for visibility of devicehealth in table + this.getExpandCollapseElement('devicehealth').click(); + for (let i = 0, devHealthTuple: [string, string]; (devHealthTuple = devHealthArray[i]); i++) { + if (devHealthTuple[0] !== undefined) { + // Repeatedly reclicks the module to check if edits has been done + cy.contains('.datatable-body-cell-label', 'devicehealth').click(); + cy.get('.datatable-body').last().contains(devHealthTuple[0]).should('be.visible'); + } + } + + // Inputs old values into devicehealth fields. This manager module doesn't allow for updates + // to be made when the values are cleared. Therefore, I restored them to their original values + // (on my local run of ceph-dev, this is subject to change i would assume). + // I'd imagine there is a better way of doing this. + this.getFirstTableCell('devicehealth').click(); + cy.contains('button', 'Edit').click(); + cy.get('#mark_out_threshold').clear().type('2419200'); + + cy.get('#pool_name').clear().type('device_health_metrics'); + + cy.get('#retention_period').clear().type('15552000'); + + cy.get('#scrape_frequency').clear().type('86400'); + + cy.get('#sleep_interval').clear().type('600'); + + cy.get('#warn_threshold').clear().type('7257600'); + + // Checks that clearing represents in details tab + cy.contains('button', 'Update').click(); + this.getExpandCollapseElement('devicehealth').should('be.visible').click(); + for (let i = 0, devHealthTuple: [string, string]; (devHealthTuple = devHealthArray[i]); i++) { + if (devHealthTuple[0] !== undefined) { + // Repeatedly reclicks the module to check if clearing has been done + cy.contains('.datatable-body-cell-label', 'devicehealth').click(); + cy.get('.datatable-body') + .eq(1) + .should('contain', devHealthTuple[1]) + .and('not.contain', devHealthTuple[0]); + } + } + } +} diff --git a/src/pybind/mgr/dashboard/frontend/cypress/integration/cluster/monitors.e2e-spec.ts b/src/pybind/mgr/dashboard/frontend/cypress/integration/cluster/monitors.e2e-spec.ts new file mode 100644 index 000000000000..8324ff8b5b05 --- /dev/null +++ b/src/pybind/mgr/dashboard/frontend/cypress/integration/cluster/monitors.e2e-spec.ts @@ -0,0 +1,61 @@ +import { MonitorsPageHelper } from './monitors.po'; + +describe('Monitors page', () => { + const monitors = new MonitorsPageHelper(); + + beforeEach(() => { + cy.login(); + monitors.navigateTo(); + }); + + describe('breadcrumb test', () => { + it('should open and show breadcrumb', () => { + monitors.expectBreadcrumbText('Monitors'); + }); + }); + + describe('fields check', () => { + it('should check status table is present', () => { + // check for table header 'Status' + monitors.getLegends().its(0).should('have.text', 'Status'); + + // check for fields in table + monitors + .getStatusTables() + .should('contain.text', 'Cluster ID') + .and('contain.text', 'monmap modified') + .and('contain.text', 'monmap epoch') + .and('contain.text', 'quorum con') + .and('contain.text', 'quorum mon') + .and('contain.text', 'required con') + .and('contain.text', 'required mon'); + }); + + it('should check In Quorum and Not In Quorum tables are present', () => { + // check for there to be two tables + monitors.getDataTables().should('have.length', 2); + + // check for table header 'In Quorum' + monitors.getLegends().its(1).should('have.text', 'In Quorum'); + + // check for table header 'Not In Quorum' + monitors.getLegends().its(2).should('have.text', 'Not In Quorum'); + + // verify correct columns on In Quorum table + monitors.getDataTableHeaders(0).contains('Name'); + + monitors.getDataTableHeaders(0).contains('Rank'); + + monitors.getDataTableHeaders(0).contains('Public Address'); + + monitors.getDataTableHeaders(0).contains('Open Sessions'); + + // verify correct columns on Not In Quorum table + monitors.getDataTableHeaders(1).contains('Name'); + + monitors.getDataTableHeaders(1).contains('Rank'); + + monitors.getDataTableHeaders(1).contains('Public Address'); + }); + }); +}); diff --git a/src/pybind/mgr/dashboard/frontend/cypress/integration/cluster/monitors.po.ts b/src/pybind/mgr/dashboard/frontend/cypress/integration/cluster/monitors.po.ts new file mode 100644 index 000000000000..4113b99288d1 --- /dev/null +++ b/src/pybind/mgr/dashboard/frontend/cypress/integration/cluster/monitors.po.ts @@ -0,0 +1,7 @@ +import { PageHelper } from '../page-helper.po'; + +export class MonitorsPageHelper extends PageHelper { + pages = { + index: { url: '#/monitor', id: 'cd-monitor' } + }; +} diff --git a/src/pybind/mgr/dashboard/frontend/cypress/integration/cluster/osds.e2e-spec.ts b/src/pybind/mgr/dashboard/frontend/cypress/integration/cluster/osds.e2e-spec.ts new file mode 100644 index 000000000000..15bc30f1f3bb --- /dev/null +++ b/src/pybind/mgr/dashboard/frontend/cypress/integration/cluster/osds.e2e-spec.ts @@ -0,0 +1,61 @@ +import { OSDsPageHelper } from './osds.po'; + +describe('OSDs page', () => { + const osds = new OSDsPageHelper(); + + beforeEach(() => { + cy.login(); + osds.navigateTo(); + }); + + describe('breadcrumb and tab tests', () => { + it('should open and show breadcrumb', () => { + osds.expectBreadcrumbText('OSDs'); + }); + + it('should show two tabs', () => { + osds.getTabsCount().should('eq', 2); + osds.getTabText(0).should('eq', 'OSDs List'); + osds.getTabText(1).should('eq', 'Overall Performance'); + }); + }); + + describe('check existence of fields on OSD page', () => { + it('should check that number of rows and count in footer match', () => { + osds.getTableTotalCount().then((text) => { + osds.getTableRows().its('length').should('equal', text); + }); + }); + + it('should verify that buttons exist', () => { + cy.contains('button', 'Create'); + cy.contains('button', 'Cluster-wide configuration'); + }); + + describe('by selecting one row in OSDs List', () => { + beforeEach(() => { + osds.getExpandCollapseElement().click(); + }); + + it('should verify that selected footer increases', () => { + osds.getTableSelectedCount().should('equal', 1); + }); + + it('should show the correct text for the tab labels', () => { + cy.get('#tabset-osd-details > div > tab').then(($tabs) => { + const tabHeadings = $tabs.map((_i, e) => e.getAttribute('heading')).get(); + + expect(tabHeadings).to.eql([ + 'Devices', + 'Attributes (OSD map)', + 'Metadata', + 'Device health', + 'Performance counter', + 'Histogram', + 'Performance Details' + ]); + }); + }); + }); + }); +}); diff --git a/src/pybind/mgr/dashboard/frontend/cypress/integration/cluster/osds.po.ts b/src/pybind/mgr/dashboard/frontend/cypress/integration/cluster/osds.po.ts new file mode 100644 index 000000000000..36e0b4feb55b --- /dev/null +++ b/src/pybind/mgr/dashboard/frontend/cypress/integration/cluster/osds.po.ts @@ -0,0 +1,5 @@ +import { PageHelper } from '../page-helper.po'; + +export class OSDsPageHelper extends PageHelper { + pages = { index: { url: '#/osd', id: 'cd-osd-list' } }; +} diff --git a/src/pybind/mgr/dashboard/frontend/cypress/integration/filesystems/filesystems.e2e-spec.ts b/src/pybind/mgr/dashboard/frontend/cypress/integration/filesystems/filesystems.e2e-spec.ts new file mode 100644 index 000000000000..63f59916f996 --- /dev/null +++ b/src/pybind/mgr/dashboard/frontend/cypress/integration/filesystems/filesystems.e2e-spec.ts @@ -0,0 +1,16 @@ +import { FilesystemsPageHelper } from './filesystems.po'; + +describe('Filesystems page', () => { + const filesystems = new FilesystemsPageHelper(); + + beforeEach(() => { + cy.login(); + filesystems.navigateTo(); + }); + + describe('breadcrumb test', () => { + it('should open and show breadcrumb', () => { + filesystems.expectBreadcrumbText('Filesystems'); + }); + }); +}); diff --git a/src/pybind/mgr/dashboard/frontend/cypress/integration/filesystems/filesystems.po.ts b/src/pybind/mgr/dashboard/frontend/cypress/integration/filesystems/filesystems.po.ts new file mode 100644 index 000000000000..bd6e5b8b7b44 --- /dev/null +++ b/src/pybind/mgr/dashboard/frontend/cypress/integration/filesystems/filesystems.po.ts @@ -0,0 +1,5 @@ +import { PageHelper } from '../page-helper.po'; + +export class FilesystemsPageHelper extends PageHelper { + pages = { index: { url: '#/cephfs', id: 'cd-cephfs-list' } }; +} diff --git a/src/pybind/mgr/dashboard/frontend/cypress/integration/nfs/nfs.e2e-spec.ts b/src/pybind/mgr/dashboard/frontend/cypress/integration/nfs/nfs.e2e-spec.ts new file mode 100644 index 000000000000..0864a5edcc12 --- /dev/null +++ b/src/pybind/mgr/dashboard/frontend/cypress/integration/nfs/nfs.e2e-spec.ts @@ -0,0 +1,16 @@ +import { NfsPageHelper } from './nfs.po'; + +describe('Nfs page', () => { + const nfs = new NfsPageHelper(); + + beforeEach(() => { + cy.login(); + nfs.navigateTo(); + }); + + describe('breadcrumb test', () => { + it('should open and show breadcrumb', () => { + nfs.expectBreadcrumbText('NFS'); + }); + }); +}); diff --git a/src/pybind/mgr/dashboard/frontend/cypress/integration/nfs/nfs.po.ts b/src/pybind/mgr/dashboard/frontend/cypress/integration/nfs/nfs.po.ts new file mode 100644 index 000000000000..7dd482a140c4 --- /dev/null +++ b/src/pybind/mgr/dashboard/frontend/cypress/integration/nfs/nfs.po.ts @@ -0,0 +1,5 @@ +import { PageHelper } from '../page-helper.po'; + +export class NfsPageHelper extends PageHelper { + pages = { index: { url: '#/nfs', id: 'cd-nfs-501' } }; +} diff --git a/src/pybind/mgr/dashboard/frontend/cypress/integration/page-helper.po.ts b/src/pybind/mgr/dashboard/frontend/cypress/integration/page-helper.po.ts new file mode 100644 index 000000000000..cff33a744137 --- /dev/null +++ b/src/pybind/mgr/dashboard/frontend/cypress/integration/page-helper.po.ts @@ -0,0 +1,248 @@ +interface Page { + url: string; + id: string; +} + +export abstract class PageHelper { + pages: Record; + + /** + * Decorator to be used on Helper methods to restrict access to one particular URL. This shall + * help developers to prevent and highlight mistakes. It also reduces boilerplate code and by + * thus, increases readability. + */ + static restrictTo(page: string): Function { + return (target: any, propertyKey: string, descriptor: PropertyDescriptor) => { + const fn: Function = descriptor.value; + descriptor.value = function (...args: any) { + cy.location('hash').should((url) => { + expect(url).to.eq( + page, + `Method ${target.constructor.name}::${propertyKey} is supposed to be ` + + `run on path "${page}", but was run on URL "${url}"` + ); + }); + fn.apply(this, args); + }; + }; + } + + /** + * Navigates to the given page or to index. + * Waits until the page component is loaded + */ + navigateTo(name: string = null) { + name = name || 'index'; + const page = this.pages[name]; + + cy.visit(page.url); + cy.get(page.id); + } + + /** + * Navigates back and waits for the hash to change + */ + navigateBack() { + cy.location('hash').then((hash) => { + cy.go('back'); + cy.location('hash').should('not.be', hash); + }); + } + + /** + * Checks the active breadcrumb value. + */ + expectBreadcrumbText(text: string) { + cy.get('.breadcrumb-item.active').should('have.text', text); + } + + getTabText(index: number) { + return cy.get('.nav.nav-tabs li').its(index).text(); + } + + getTabsCount(): any { + return cy.get('.nav.nav-tabs li').its('length'); + } + + /** + * Helper method to select an option inside a select element. + * This method will also expect that the option was set. + * @param option The option text (not value) to be selected. + */ + selectOption(selectionName: string, option: string) { + cy.get(`select[name=${selectionName}]`).select(option); + return this.expectSelectOption(selectionName, option); + } + + /** + * Helper method to expect a set option inside a select element. + * @param option The selected option text (not value) that is to + * be expected. + */ + expectSelectOption(selectionName: string, option: string) { + return cy.get(`select[name=${selectionName}] option:checked`).contains(option); + } + + getLegends() { + return cy.get('legend'); + } + + getToast() { + return cy.get('.ngx-toastr'); + } + + /** + * Waits for the table to load its data + * Should be used in all methods that access the datatable + */ + private waitDataTableToLoad() { + cy.get('cd-table').should('exist'); + cy.get('datatable-scroller, .empty-row'); + } + + getDataTables() { + this.waitDataTableToLoad(); + + return cy.get('cd-table .dataTables_wrapper'); + } + + getTableTotalCount() { + this.waitDataTableToLoad(); + + return cy.get('.datatable-footer-inner .page-count span').then(($elem) => { + const text = $elem + .filter((_i, e) => e.innerText.includes('total')) + .first() + .text(); + + return Number(text.match(/(\d+)\s+total/)[1]); + }); + } + + getTableSelectedCount() { + this.waitDataTableToLoad(); + + return cy.get('.datatable-footer-inner .page-count span').then(($elem) => { + const text = $elem + .filter((_i, e) => e.innerText.includes('selected')) + .first() + .text(); + + return Number(text.match(/(\d+)\s+selected/)[1]); + }); + } + + getTableFoundCount() { + this.waitDataTableToLoad(); + + return cy.get('.datatable-footer-inner .page-count span').then(($elem) => { + const text = $elem + .filter((_i, e) => e.innerText.includes('found')) + .first() + .text(); + + return Number(text.match(/(\d+)\s+found/)[1]); + }); + } + + getTableRow(content: string) { + this.waitDataTableToLoad(); + + this.seachTable(content); + return cy.contains('.datatable-body-row', content); + } + + getTableRows() { + this.waitDataTableToLoad(); + + return cy.get('datatable-row-wrapper'); + } + + /** + * Returns the first table cell. + * Optionally, you can specify the content of the cell. + */ + getFirstTableCell(content?: string) { + this.waitDataTableToLoad(); + + if (content) { + this.seachTable(content); + return cy.contains('.datatable-body-cell-label', content); + } else { + return cy.get('.datatable-body-cell-label').first(); + } + } + + getExpandCollapseElement(content?: string) { + this.waitDataTableToLoad(); + + if (content) { + return cy.contains('.datatable-body-row', content).find('.tc_expand-collapse'); + } else { + return cy.get('.tc_expand-collapse').first(); + } + } + + /** + * Gets column headers of table + */ + getDataTableHeaders(index = 0) { + this.waitDataTableToLoad(); + + return cy.get('.datatable-header').its(index).find('.datatable-header-cell-label'); + } + + /** + * Grabs striped tables + */ + getStatusTables() { + return cy.get('.table.table-striped'); + } + + filterTable(name: string, option: string) { + this.waitDataTableToLoad(); + + cy.get('.tc_filter_name > a').click(); + cy.contains(`.tc_filter_name .dropdown-item`, name).click(); + + cy.get('.tc_filter_option > a').click(); + cy.contains(`.tc_filter_option .dropdown-item`, option).click(); + } + + seachTable(text: string) { + this.waitDataTableToLoad(); + + cy.get('cd-table .dataTables_paginate input').first().clear().type('10'); + cy.get('cd-table .search input').first().clear().type(text); + } + + clearTableSearchInput() { + this.waitDataTableToLoad(); + + return cy.get('cd-table .search button').click(); + } + + /** + * This is a generic method to delete table rows. + * It will select the first row that contains the provided name and delete it. + * After that it will wait until the row is no longer displayed. + */ + delete(name: string) { + // Selects row + this.getFirstTableCell(name).click(); + + // Clicks on table Delete button + cy.get('.table-actions button.dropdown-toggle').first().click(); // open submenu + cy.get('li.delete a').click(); // click on "delete" menu item + + // Confirms deletion + cy.get('.custom-control-label').click(); + cy.contains('button', 'Delete').click(); + + // Wait for modal to close + cy.get('cd-modal').should('not.exist'); + + // Waits for item to be removed from table + this.getFirstTableCell(name).should('not.exist'); + } +} diff --git a/src/pybind/mgr/dashboard/frontend/cypress/integration/pools/pools.e2e-spec.ts b/src/pybind/mgr/dashboard/frontend/cypress/integration/pools/pools.e2e-spec.ts new file mode 100644 index 000000000000..fd2217579a52 --- /dev/null +++ b/src/pybind/mgr/dashboard/frontend/cypress/integration/pools/pools.e2e-spec.ts @@ -0,0 +1,47 @@ +import { PoolPageHelper } from './pools.po'; + +describe('Pools page', () => { + const pools = new PoolPageHelper(); + const poolName = 'pool_e2e_pool/test'; + + beforeEach(() => { + cy.login(); + pools.navigateTo(); + }); + + describe('breadcrumb and tab tests', () => { + it('should open and show breadcrumb', () => { + pools.expectBreadcrumbText('Pools'); + }); + + it('should show two tabs', () => { + pools.getTabsCount().should('equal', 2); + }); + + it('should show pools list tab at first', () => { + pools.getTabText(0).should('eq', 'Pools List'); + }); + + it('should show overall performance as a second tab', () => { + pools.getTabText(1).should('eq', 'Overall Performance'); + }); + }); + + describe('Create, update and destroy', () => { + it('should create a pool', () => { + pools.exist(poolName, false); + pools.navigateTo('create'); + pools.create(poolName, 8); + pools.exist(poolName, true); + }); + + it('should edit a pools placement group', () => { + pools.exist(poolName, true); + pools.edit_pool_pg(poolName, 32); + }); + + it('should delete a pool', () => { + pools.delete(poolName); + }); + }); +}); diff --git a/src/pybind/mgr/dashboard/frontend/cypress/integration/pools/pools.po.ts b/src/pybind/mgr/dashboard/frontend/cypress/integration/pools/pools.po.ts new file mode 100644 index 000000000000..24f4d4244bf8 --- /dev/null +++ b/src/pybind/mgr/dashboard/frontend/cypress/integration/pools/pools.po.ts @@ -0,0 +1,59 @@ +import { PageHelper } from '../page-helper.po'; + +const pages = { + index: { url: '#/pool', id: 'cd-pool-list' }, + create: { url: '#/pool/create', id: 'cd-pool-form' } +}; + +export class PoolPageHelper extends PageHelper { + pages = pages; + + private isPowerOf2(n: number) { + // tslint:disable-next-line: no-bitwise + return expect((n & (n - 1)) === 0, `Placement groups ${n} are not a power of 2`).to.be.true; + } + + @PageHelper.restrictTo(pages.index.url) + exist(name: string, oughtToBePresent = true) { + const waitRule = oughtToBePresent ? 'be.visible' : 'not.exist'; + this.getFirstTableCell(name).should(waitRule); + } + + @PageHelper.restrictTo(pages.create.url) + create(name: string, placement_groups: number, ...apps: string[]) { + cy.get('input[name=name]').clear().type(name); + + this.isPowerOf2(placement_groups); + + this.selectOption('poolType', 'replicated'); + + this.expectSelectOption('pgAutoscaleMode', 'on'); + this.selectOption('pgAutoscaleMode', 'off'); // To show pgNum field + cy.get('input[name=pgNum]').clear().type(`${placement_groups}`); + this.setApplications(apps); + cy.get('cd-submit-button').click(); + } + + edit_pool_pg(name: string, new_pg: number, wait = true) { + this.isPowerOf2(new_pg); + this.getFirstTableCell(name).click(); // select pool from the table + cy.contains('button', 'Edit').click(); // click edit button + this.expectBreadcrumbText('Edit'); // verify we are now on edit page + cy.get('input[name=pgNum]').clear().type(`${new_pg}`); + cy.get('cd-submit-button').click(); + const str = `${new_pg} active+clean`; + this.getTableRow(name); + if (wait) { + this.getTableRow(name).contains(str); + } + } + + private setApplications(apps: string[]) { + if (!apps || apps.length === 0) { + return; + } + cy.get('.float-left.mr-2.select-menu-edit').click(); + cy.get('.popover-content.popover-body').should('be.visible'); + apps.forEach((app) => cy.get('.select-menu-item-content').contains(app).click()); + } +} diff --git a/src/pybind/mgr/dashboard/frontend/cypress/integration/rgw/buckets.e2e-spec.ts b/src/pybind/mgr/dashboard/frontend/cypress/integration/rgw/buckets.e2e-spec.ts new file mode 100644 index 000000000000..e5e0daa4c1f5 --- /dev/null +++ b/src/pybind/mgr/dashboard/frontend/cypress/integration/rgw/buckets.e2e-spec.ts @@ -0,0 +1,56 @@ +import { BucketsPageHelper } from './buckets.po'; + +describe('RGW buckets page', () => { + const buckets = new BucketsPageHelper(); + const bucket_name = 'e2ebucket'; + + beforeEach(() => { + cy.login(); + buckets.navigateTo(); + }); + + describe('breadcrumb tests', () => { + it('should open and show breadcrumb', () => { + buckets.expectBreadcrumbText('Buckets'); + }); + }); + + describe('create, edit & delete bucket tests', () => { + it('should create bucket', () => { + buckets.navigateTo('create'); + buckets.create( + bucket_name, + '0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef', + 'default-placement' + ); + buckets.getFirstTableCell(bucket_name).should('exist'); + }); + + it('should edit bucket', () => { + buckets.edit(bucket_name, 'dev'); + buckets.getDataTables().should('contain.text', 'dev'); + }); + + it('should delete bucket', () => { + buckets.delete(bucket_name); + }); + }); + + describe('Invalid Input in Create and Edit tests', () => { + it('should test invalid inputs in create fields', () => { + buckets.testInvalidCreate(); + }); + + it('should test invalid input in edit owner field', () => { + buckets.navigateTo('create'); + buckets.create( + bucket_name, + '0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef', + 'default-placement' + ); + buckets.testInvalidEdit(bucket_name); + buckets.navigateTo(); + buckets.delete(bucket_name); + }); + }); +}); diff --git a/src/pybind/mgr/dashboard/frontend/cypress/integration/rgw/buckets.po.ts b/src/pybind/mgr/dashboard/frontend/cypress/integration/rgw/buckets.po.ts new file mode 100644 index 000000000000..7d1f6e55d065 --- /dev/null +++ b/src/pybind/mgr/dashboard/frontend/cypress/integration/rgw/buckets.po.ts @@ -0,0 +1,162 @@ +import { PageHelper } from '../page-helper.po'; + +const pages = { + index: { url: '#/rgw/bucket', id: 'cd-rgw-bucket-list' }, + create: { url: '#/rgw/bucket/create', id: 'cd-rgw-bucket-form' } +}; + +export class BucketsPageHelper extends PageHelper { + pages = pages; + + versioningStateEnabled = 'Enabled'; + versioningStateSuspended = 'Suspended'; + + private selectOwner(owner: string) { + return this.selectOption('owner', owner); + } + + private selectPlacementTarget(placementTarget: string) { + return this.selectOption('placement-target', placementTarget); + } + + @PageHelper.restrictTo(pages.create.url) + create(name: string, owner: string, placementTarget: string) { + // Enter in bucket name + cy.get('#bid').type(name); + + // Select bucket owner + this.selectOwner(owner); + cy.get('#owner').should('have.class', 'ng-valid'); + + // Select bucket placement target: + this.selectPlacementTarget(placementTarget); + cy.get('#placement-target').should('have.class', 'ng-valid'); + + // Click the create button and wait for bucket to be made + cy.contains('button', 'Create Bucket').click(); + + this.getFirstTableCell(name).should('exist'); + } + + @PageHelper.restrictTo(pages.index.url) + edit(name: string, new_owner: string) { + this.getFirstTableCell(name).click(); // wait for table to load and click + cy.contains('button', 'Edit').click(); // click button to move to edit page + this.expectBreadcrumbText('Edit'); + cy.get('input[name=placement-target]').should('have.value', 'default-placement'); + this.selectOwner(new_owner); + + // Enable versioning + cy.get('input[id=versioning]').should('not.be.checked'); + cy.get('label[for=versioning]').click(); + cy.get('input[id=versioning]').should('be.checked'); + + cy.contains('button', 'Edit Bucket').click(); + + // wait to be back on buckets page with table visible and click + this.getExpandCollapseElement(name).click(); + + // check its details table for edited owner field + cy.get('.table.table-striped.table-bordered') + .first() + .should('contains.text', new_owner) + .as('bucketDataTable'); + + // Check versioning enabled: + cy.get('@bucketDataTable').find('tr').its(2).find('td').last().should('have.text', new_owner); + cy.get('@bucketDataTable').find('tr').its(11).find('td').last().as('versioningValueCell'); + + cy.get('@versioningValueCell').should('have.text', this.versioningStateEnabled); + + // Disable versioning: + cy.contains('button', 'Edit').click(); // click button to move to edit page + this.expectBreadcrumbText('Edit'); + cy.get('label[for=versioning]').click(); + cy.get('input[id=versioning]').should('not.be.checked'); + cy.contains('button', 'Edit Bucket').click(); + + // Check versioning suspended: + this.getExpandCollapseElement(name).click(); + + return cy.get('@versioningValueCell').should('have.text', this.versioningStateSuspended); + } + + testInvalidCreate() { + this.navigateTo('create'); + cy.get('#bid').as('nameInputField'); // Grabs name box field + + // Gives an invalid name (too short), then waits for dashboard to determine validity + cy.get('@nameInputField').type('rq'); + + cy.contains('button', 'Create Bucket').click(); // To trigger a validation + + // Waiting for website to decide if name is valid or not + // Check that name input field was marked invalid in the css + cy.get('@nameInputField') + .should('not.have.class', 'ng-pending') + .and('have.class', 'ng-invalid'); + + // Check that error message was printed under name input field + cy.get('#bid + .invalid-feedback').should('have.text', 'The value is not valid.'); + + // Test invalid owner input + // select some valid option. The owner drop down error message will not appear unless a valid user was selected at + // one point before the invalid placeholder user is selected. + this.selectOwner('dev'); + + // select the first option, which is invalid because it is a placeholder + this.selectOwner('-- Select a user --'); + + cy.get('@nameInputField').click(); + + // Check that owner drop down field was marked invalid in the css + cy.get('#owner').should('have.class', 'ng-invalid'); + + // Check that error message was printed under owner drop down field + cy.get('#owner + .invalid-feedback').should('have.text', 'This field is required.'); + + // Check invalid placement target input + this.selectOwner('dev'); + // The drop down error message will not appear unless a valid option is previsously selected. + this.selectPlacementTarget('default-placement'); + this.selectPlacementTarget('-- Select a placement target --'); + cy.get('@nameInputField').click(); // Trigger validation + cy.get('#placement-target').should('have.class', 'ng-invalid'); + cy.get('#placement-target + .invalid-feedback').should('have.text', 'This field is required.'); + + // Clicks the Create Bucket button but the page doesn't move. + // Done by testing for the breadcrumb + cy.contains('button', 'Create Bucket').click(); // Clicks Create Bucket button + this.expectBreadcrumbText('Create'); + // content in fields seems to subsist through tests if not cleared, so it is cleared + cy.get('@nameInputField').clear(); + return cy.contains('button', 'Cancel').click(); + } + + testInvalidEdit(name: string) { + this.navigateTo(); + + this.getFirstTableCell(name).click(); // wait for table to load and click + cy.contains('button', 'Edit').click(); // click button to move to edit page + + this.expectBreadcrumbText('Edit'); + + cy.get('input[id=versioning]').should('exist').and('not.be.checked'); + + // Chooses 'Select a user' rather than a valid owner on Edit Bucket page + // and checks if it's an invalid input + + // select the first option, which is invalid because it is a placeholder + this.selectOwner('-- Select a user --'); + + cy.contains('button', 'Edit Bucket').click(); + + // Check that owner drop down field was marked invalid in the css + cy.get('#owner').should('have.class', 'ng-invalid'); + + // Check that error message was printed under owner drop down field + cy.get('#owner + .invalid-feedback').should('have.text', 'This field is required.'); + + this.expectBreadcrumbText('Edit'); + } +} diff --git a/src/pybind/mgr/dashboard/frontend/cypress/integration/rgw/daemons.e2e-spec.ts b/src/pybind/mgr/dashboard/frontend/cypress/integration/rgw/daemons.e2e-spec.ts new file mode 100644 index 000000000000..03b2ca834219 --- /dev/null +++ b/src/pybind/mgr/dashboard/frontend/cypress/integration/rgw/daemons.e2e-spec.ts @@ -0,0 +1,34 @@ +import { DaemonsPageHelper } from './daemons.po'; + +describe('RGW daemons page', () => { + const daemons = new DaemonsPageHelper(); + + beforeEach(() => { + cy.login(); + daemons.navigateTo(); + }); + + describe('breadcrumb and tab tests', () => { + it('should open and show breadcrumb', () => { + daemons.expectBreadcrumbText('Daemons'); + }); + + it('should show two tabs', () => { + daemons.getTabsCount().should('eq', 2); + }); + + it('should show daemons list tab at first', () => { + daemons.getTabText(0).should('eq', 'Daemons List'); + }); + + it('should show overall performance as a second tab', () => { + daemons.getTabText(1).should('eq', 'Overall Performance'); + }); + }); + + describe('details and performance counters table tests', () => { + it('should check that details/performance tables are visible when daemon is selected', () => { + daemons.checkTables(); + }); + }); +}); diff --git a/src/pybind/mgr/dashboard/frontend/cypress/integration/rgw/daemons.po.ts b/src/pybind/mgr/dashboard/frontend/cypress/integration/rgw/daemons.po.ts new file mode 100644 index 000000000000..0ca60665db40 --- /dev/null +++ b/src/pybind/mgr/dashboard/frontend/cypress/integration/rgw/daemons.po.ts @@ -0,0 +1,42 @@ +import { PageHelper } from '../page-helper.po'; + +export class DaemonsPageHelper extends PageHelper { + pages = { + index: { url: '#/rgw/daemon', id: 'cd-rgw-daemon-list' } + }; + + getTableCell(tableIndex: number) { + return cy + .get('.tab-container') + .its(1) + .find('cd-table') + .its(tableIndex) + .find('datatable-body-cell'); + } + + checkTables() { + // click on a daemon so details table appears + cy.get('.datatable-body-cell-label').first().click(); + + // check details table is visible + // check at least one field is present + this.getTableCell(0).should('visible').should('contain.text', 'ceph_version'); + // check performance counters table is not currently visible + this.getTableCell(1).should('not.be.visible'); + + // click on performance counters tab and check table is loaded + cy.contains('.nav-link', 'Performance Counters').click(); + + // check at least one field is present + this.getTableCell(1).should('be.visible').should('contain.text', 'objecter.op_r'); + // check details table is not currently visible + this.getTableCell(0).should('not.be.visible'); + + // click on performance details tab + cy.contains('.nav-link', 'Performance Details').click(); + + // checks the other tabs' content isn't visible + this.getTableCell(0).should('not.be.visible'); + this.getTableCell(1).should('not.be.visible'); + } +} diff --git a/src/pybind/mgr/dashboard/frontend/cypress/integration/rgw/users.e2e-spec.ts b/src/pybind/mgr/dashboard/frontend/cypress/integration/rgw/users.e2e-spec.ts new file mode 100644 index 000000000000..a8d7d45b4190 --- /dev/null +++ b/src/pybind/mgr/dashboard/frontend/cypress/integration/rgw/users.e2e-spec.ts @@ -0,0 +1,43 @@ +import { UsersPageHelper } from './users.po'; + +describe('RGW users page', () => { + const users = new UsersPageHelper(); + const user_name = 'e2e_000user_create_edit_delete'; + + beforeEach(() => { + cy.login(); + users.navigateTo(); + }); + + describe('breadcrumb tests', () => { + it('should open and show breadcrumb', () => { + users.expectBreadcrumbText('Users'); + }); + }); + + describe('create, edit & delete user tests', () => { + it('should create user', () => { + users.navigateTo('create'); + users.create(user_name, 'Some Name', 'original@website.com', '1200'); + users.getFirstTableCell(user_name).should('exist'); + }); + + it('should edit users full name, email and max buckets', () => { + users.edit(user_name, 'Another Identity', 'changed@othersite.com', '1969'); + }); + + it('should delete user', () => { + users.delete(user_name); + }); + }); + + describe('Invalid input tests', () => { + it('should put invalid input into user creation form and check fields are marked invalid', () => { + users.invalidCreate(); + }); + + it('should put invalid input into user edit form and check fields are marked invalid', () => { + users.invalidEdit(); + }); + }); +}); diff --git a/src/pybind/mgr/dashboard/frontend/cypress/integration/rgw/users.po.ts b/src/pybind/mgr/dashboard/frontend/cypress/integration/rgw/users.po.ts new file mode 100644 index 000000000000..66cff75dc289 --- /dev/null +++ b/src/pybind/mgr/dashboard/frontend/cypress/integration/rgw/users.po.ts @@ -0,0 +1,139 @@ +import { PageHelper } from '../page-helper.po'; + +const pages = { + index: { url: '#/rgw/user', id: 'cd-rgw-user-list' }, + create: { url: '#/rgw/user/create', id: 'cd-rgw-user-form' } +}; + +export class UsersPageHelper extends PageHelper { + pages = pages; + + @PageHelper.restrictTo(pages.create.url) + create(username: string, fullname: string, email: string, maxbuckets: string) { + // Enter in username + cy.get('#uid').type(username); + + // Enter in full name + cy.get('#display_name').click().type(fullname); + + // Enter in email + cy.get('#email').click().type(email); + + // Enter max buckets + this.selectOption('max_buckets_mode', 'Custom'); + cy.get('#max_buckets').click().clear().type(maxbuckets); + + // Click the create button and wait for user to be made + cy.contains('button', 'Create User').click(); + this.getFirstTableCell(username).should('exist'); + } + + @PageHelper.restrictTo(pages.index.url) + edit(name: string, new_fullname: string, new_email: string, new_maxbuckets: string) { + this.getFirstTableCell(name).click(); // wait for table to load and click + cy.contains('button', 'Edit').click(); // click button to move to edit page + + this.expectBreadcrumbText('Edit'); + + // Change the full name field + cy.get('#display_name').click().clear().type(new_fullname); + + // Change the email field + cy.get('#email').click().clear().type(new_email); + + // Change the max buckets field + this.selectOption('max_buckets_mode', 'Custom'); + cy.get('#max_buckets').click().clear().type(new_maxbuckets); + + cy.contains('button', 'Edit User').click(); + + // Click the user and check its details table for updated content + this.getExpandCollapseElement(name).click(); + cy.get('.active.tab-pane') + .should('contain.text', new_fullname) + .and('contain.text', new_email) + .and('contain.text', new_maxbuckets); + } + + invalidCreate() { + const uname = '000invalid_create_user'; + // creating this user in order to check that you can't give two users the same name + this.navigateTo('create'); + this.create(uname, 'xxx', 'xxx@xxx', '1'); + + this.navigateTo('create'); + + // Username + cy.get('#uid') + // No username had been entered. Field should be invalid + .should('have.class', 'ng-invalid') + // Try to give user already taken name. Should make field invalid. + .type(uname) + .blur() + .should('have.class', 'ng-invalid'); + cy.contains('#uid + .invalid-feedback', 'The chosen user ID is already in use.'); + + // check that username field is marked invalid if username has been cleared off + cy.get('#uid').clear().blur().should('have.class', 'ng-invalid'); + cy.contains('#uid + .invalid-feedback', 'This field is required.'); + + // Full name + cy.get('#display_name') + // No display name has been given so field should be invalid + .should('have.class', 'ng-invalid') + // display name field should also be marked invalid if given input then emptied + .type('a') + .clear() + .blur() + .should('have.class', 'ng-invalid'); + cy.contains('#display_name + .invalid-feedback', 'This field is required.'); + + // put invalid email to make field invalid + cy.get('#email').type('a').blur().should('have.class', 'ng-invalid'); + cy.contains('#email + .invalid-feedback', 'This is not a valid email address.'); + + // put negative max buckets to make field invalid + this.expectSelectOption('max_buckets_mode', 'Custom'); + cy.get('#max_buckets').clear().type('-5').blur().should('have.class', 'ng-invalid'); + cy.contains('#max_buckets + .invalid-feedback', 'The entered value must be >= 1.'); + + this.navigateTo(); + this.delete(uname); + } + + invalidEdit() { + const uname = '000invalid_edit_user'; + // creating this user to edit for the test + this.navigateTo('create'); + this.create(uname, 'xxx', 'xxx@xxx', '1'); + + this.navigateTo(); + + // wait for table to load and click on the bucket you want to edit in the table + this.getFirstTableCell(uname).click(); + cy.contains('button', 'Edit').click(); // click button to move to edit page + + this.expectBreadcrumbText('Edit'); + + // put invalid email to make field invalid + cy.get('#email') + .clear() + .type('a') + .blur() + .should('not.have.class', 'ng-pending') + .should('have.class', 'ng-invalid'); + cy.contains('#email + .invalid-feedback', 'This is not a valid email address.'); + + // empty the display name field making it invalid + cy.get('#display_name').clear().blur().should('have.class', 'ng-invalid'); + cy.contains('#display_name + .invalid-feedback', 'This field is required.'); + + // put negative max buckets to make field invalid + this.expectSelectOption('max_buckets_mode', 'Custom'); + cy.get('#max_buckets').clear().type('-5').blur().should('have.class', 'ng-invalid'); + cy.contains('#max_buckets + .invalid-feedback', 'The entered value must be >= 1.'); + + this.navigateTo(); + this.delete(uname); + } +} diff --git a/src/pybind/mgr/dashboard/frontend/cypress/integration/ui/dashboard.e2e-spec.ts b/src/pybind/mgr/dashboard/frontend/cypress/integration/ui/dashboard.e2e-spec.ts new file mode 100644 index 000000000000..f149a4b0ab34 --- /dev/null +++ b/src/pybind/mgr/dashboard/frontend/cypress/integration/ui/dashboard.e2e-spec.ts @@ -0,0 +1,124 @@ +import { IscsiPageHelper } from '../block/iscsi.po'; +import { HostsPageHelper } from '../cluster/hosts.po'; +import { MonitorsPageHelper } from '../cluster/monitors.po'; +import { OSDsPageHelper } from '../cluster/osds.po'; +import { PageHelper } from '../page-helper.po'; +import { PoolPageHelper } from '../pools/pools.po'; +import { DaemonsPageHelper } from '../rgw/daemons.po'; +import { DashboardPageHelper } from './dashboard.po'; + +describe('Dashboard Main Page', () => { + const dashboard = new DashboardPageHelper(); + const daemons = new DaemonsPageHelper(); + const hosts = new HostsPageHelper(); + const osds = new OSDsPageHelper(); + const pools = new PoolPageHelper(); + const monitors = new MonitorsPageHelper(); + const iscsi = new IscsiPageHelper(); + + beforeEach(() => { + cy.login(); + dashboard.navigateTo(); + }); + + describe('Check that all hyperlinks on info cards lead to the correct page and fields exist', () => { + it('should ensure that all linked info cards lead to correct page', () => { + const expectationMap = { + Monitors: 'Monitors', + OSDs: 'OSDs', + Hosts: 'Hosts', + 'Object Gateways': 'Daemons', + 'iSCSI Gateways': 'Overview', + Pools: 'Pools' + }; + + for (const [linkText, breadcrumbText] of Object.entries(expectationMap)) { + cy.location('hash').should('eq', '#/dashboard'); + dashboard.clickInfoCardLink(linkText); + dashboard.expectBreadcrumbText(breadcrumbText); + dashboard.navigateBack(); + } + }); + + it('should verify that info cards exist on dashboard in proper order', () => { + // Ensures that info cards are all displayed on the dashboard tab while being in the proper + // order, checks for card title and position via indexing into a list of all info cards. + const order = [ + 'Cluster Status', + 'Monitors', + 'OSDs', + 'Manager Daemons', + 'Hosts', + 'Object Gateways', + 'Metadata Servers', + 'iSCSI Gateways', + 'Client IOPS', + 'Client Throughput', + 'Client Read/Write', + 'Recovery Throughput', + 'Scrub', + 'Pools', + 'Raw Capacity', + 'Objects', + 'PGs per OSD', + 'PG Status' + ]; + + for (let i = 0; i < order.length; i++) { + dashboard.infoCard(i).should('contain.text', order[i]); + } + }); + + it('should verify that info card group titles are present and in the right order', () => { + cy.location('hash').should('eq', '#/dashboard'); + dashboard.infoGroupTitle(0).should('eq', 'Status'); + dashboard.infoGroupTitle(1).should('eq', 'Performance'); + dashboard.infoGroupTitle(2).should('eq', 'Capacity'); + }); + }); + + it('Should check that dashboard cards have correct information', () => { + interface TestSpec { + cardName: string; + regexMatcher?: RegExp; + pageObject: PageHelper; + } + const testSpecs: TestSpec[] = [ + { cardName: 'Object Gateways', regexMatcher: /(\d+)\s+total/, pageObject: daemons }, + { cardName: 'Monitors', regexMatcher: /(\d+)\s+\(quorum/, pageObject: monitors }, + { cardName: 'Hosts', regexMatcher: /(\d+)\s+total/, pageObject: hosts }, + { cardName: 'OSDs', regexMatcher: /(\d+)\s+total/, pageObject: osds }, + { cardName: 'Pools', pageObject: pools }, + { cardName: 'iSCSI Gateways', regexMatcher: /(\d+)\s+total/, pageObject: iscsi } + ]; + for (let i = 0; i < testSpecs.length; i++) { + const spec = testSpecs[i]; + dashboard.navigateTo(); + + dashboard.infoCardBodyText(spec.cardName).then((infoCardBodyText: string) => { + let dashCount = 0; + + if (spec.regexMatcher) { + const match = infoCardBodyText.match(new RegExp(spec.regexMatcher)); + expect(match).to.length.gt( + 1, + `Regex ${spec.regexMatcher} did not find a match for card with name ` + + `${spec.cardName}` + ); + dashCount = Number(match[1]); + } else { + dashCount = Number(infoCardBodyText); + } + + spec.pageObject.navigateTo(); + spec.pageObject.getTableTotalCount().then((tableCount) => { + expect(tableCount).to.eq( + dashCount, + `Text of card "${spec.cardName}" and regex "${spec.regexMatcher}" resulted in ${dashCount} ` + + `but did not match table count ${tableCount}` + ); + }); + }); + } + }); +}); diff --git a/src/pybind/mgr/dashboard/frontend/cypress/integration/ui/dashboard.po.ts b/src/pybind/mgr/dashboard/frontend/cypress/integration/ui/dashboard.po.ts new file mode 100644 index 000000000000..02125739723e --- /dev/null +++ b/src/pybind/mgr/dashboard/frontend/cypress/integration/ui/dashboard.po.ts @@ -0,0 +1,27 @@ +import { PageHelper } from '../page-helper.po'; + +export class DashboardPageHelper extends PageHelper { + pages = { index: { url: '#/dashboard', id: 'cd-dashboard' } }; + + infoGroupTitle(index: number) { + return cy.get('.info-group-title').its(index).text(); + } + + clickInfoCardLink(cardName: string) { + cy.get(`cd-info-card[cardtitle="${cardName}"]`).contains('a', cardName).click(); + } + + infoCard(indexOrTitle: number | string) { + cy.get('cd-info-card').as('infoCards'); + + if (typeof indexOrTitle === 'number') { + return cy.get('@infoCards').its(indexOrTitle); + } else { + return cy.contains('cd-info-card a', indexOrTitle).parent().parent().parent().parent(); + } + } + + infoCardBodyText(infoCard: string) { + return this.infoCard(infoCard).find('.card-text').text(); + } +} diff --git a/src/pybind/mgr/dashboard/frontend/cypress/integration/ui/notification.e2e-spec.ts b/src/pybind/mgr/dashboard/frontend/cypress/integration/ui/notification.e2e-spec.ts new file mode 100644 index 000000000000..b69f26f58dc3 --- /dev/null +++ b/src/pybind/mgr/dashboard/frontend/cypress/integration/ui/notification.e2e-spec.ts @@ -0,0 +1,56 @@ +import { PoolPageHelper } from '../pools/pools.po'; +import { NotificationSidebarPageHelper } from './notification.po'; + +describe('Notification page', () => { + const notification = new NotificationSidebarPageHelper(); + const pools = new PoolPageHelper(); + const poolName = 'e2e_notification_pool'; + + before(() => { + cy.login(); + pools.navigateTo('create'); + pools.create(poolName, 8); + pools.edit_pool_pg(poolName, 4, false); + }); + + after(() => { + cy.login(); + pools.navigateTo(); + pools.delete(poolName); + }); + + beforeEach(() => { + cy.login(); + pools.navigateTo(); + }); + + it('should open notification sidebar', () => { + notification.getSidebar().should('not.be.visible'); + notification.open(); + notification.getSidebar().should('be.visible'); + }); + + it('should display a running task', () => { + notification.getToast().should('not.exist'); + + // Check that running task is shown. + notification.open(); + notification.getTasks().contains(poolName).should('exist'); + + // Delete pool after task is complete (otherwise we get an error). + notification.getTasks().contains(poolName, { timeout: 300000 }).should('not.exist'); + }); + + it('should have notifications', () => { + notification.open(); + notification.getNotifications().should('have.length.gt', 0); + }); + + it('should clear notifications', () => { + notification.getToast().should('not.exist'); + notification.open(); + notification.getNotifications().should('have.length.gt', 0); + notification.getClearNotficationsBtn().should('be.visible'); + notification.clearNotifications(); + }); +}); diff --git a/src/pybind/mgr/dashboard/frontend/cypress/integration/ui/notification.po.ts b/src/pybind/mgr/dashboard/frontend/cypress/integration/ui/notification.po.ts new file mode 100644 index 000000000000..12c424e350d7 --- /dev/null +++ b/src/pybind/mgr/dashboard/frontend/cypress/integration/ui/notification.po.ts @@ -0,0 +1,45 @@ +import { PageHelper } from '../page-helper.po'; + +export class NotificationSidebarPageHelper extends PageHelper { + getNotificatinoIcon() { + return cy.get('cd-notifications a'); + } + + getSidebar() { + return cy.get('cd-notifications-sidebar'); + } + + getTasks() { + return this.getSidebar().find('.card.tc_task'); + } + + getNotifications() { + return this.getSidebar().find('.card.tc_notification'); + } + + getClearNotficationsBtn() { + return this.getSidebar().find('button.btn-block'); + } + + getCloseBtn() { + return this.getSidebar().find('button.close'); + } + + open() { + this.getNotificatinoIcon().click(); + this.getSidebar().should('be.visible'); + } + + clearNotifications() { + // It can happen that although notifications are cleared, by the time we check the notifications + // amount, another notification can appear, so we check it more than once (if needed). + this.getClearNotficationsBtn().click(); + this.getNotifications() + .should('have.length.gte', 0) + .then(($elems) => { + if ($elems.length > 0) { + this.clearNotifications(); + } + }); + } +} diff --git a/src/pybind/mgr/dashboard/frontend/cypress/integration/ui/role-mgmt.e2e-spec.ts b/src/pybind/mgr/dashboard/frontend/cypress/integration/ui/role-mgmt.e2e-spec.ts new file mode 100644 index 000000000000..7e76f168e6df --- /dev/null +++ b/src/pybind/mgr/dashboard/frontend/cypress/integration/ui/role-mgmt.e2e-spec.ts @@ -0,0 +1,36 @@ +import { RoleMgmtPageHelper } from './role-mgmt.po'; + +describe('Role Management page', () => { + const roleMgmt = new RoleMgmtPageHelper(); + const role_name = 'e2e_role_mgmt_role'; + + beforeEach(() => { + cy.login(); + roleMgmt.navigateTo(); + }); + + describe('breadcrumb tests', () => { + it('should check breadcrumb on roles tab on user management page', () => { + roleMgmt.expectBreadcrumbText('Roles'); + }); + + it('should check breadcrumb on role creation page', () => { + roleMgmt.navigateTo('create'); + roleMgmt.expectBreadcrumbText('Create'); + }); + }); + + describe('role create, edit & delete test', () => { + it('should create a role', () => { + roleMgmt.create(role_name, 'An interesting description'); + }); + + it('should edit a role', () => { + roleMgmt.edit(role_name, 'A far more interesting description'); + }); + + it('should delete a role', () => { + roleMgmt.delete(role_name); + }); + }); +}); diff --git a/src/pybind/mgr/dashboard/frontend/cypress/integration/ui/role-mgmt.po.ts b/src/pybind/mgr/dashboard/frontend/cypress/integration/ui/role-mgmt.po.ts new file mode 100644 index 000000000000..b90da2373802 --- /dev/null +++ b/src/pybind/mgr/dashboard/frontend/cypress/integration/ui/role-mgmt.po.ts @@ -0,0 +1,35 @@ +import { PageHelper } from '../page-helper.po'; + +export class RoleMgmtPageHelper extends PageHelper { + pages = { + index: { url: '#/user-management/roles', id: 'cd-role-list' }, + create: { url: '#/user-management/roles/create', id: 'cd-role-form' } + }; + + create(name: string, description: string) { + this.navigateTo('create'); + + // fill in fields + cy.get('#name').type(name); + cy.get('#description').type(description); + + // Click the create button and wait for role to be made + cy.contains('button', 'Create Role').click(); + + this.getFirstTableCell(name).should('exist'); + } + + edit(name: string, description: string) { + this.getFirstTableCell(name).click(); // select role from table + cy.contains('button', 'Edit').click(); // click button to move to edit page + + // fill in fields with new values + cy.get('#description').clear().type(description); + + // Click the edit button and check new values are present in table + cy.contains('button', 'Edit Role').click(); + + this.getFirstTableCell(name).should('exist'); + this.getFirstTableCell(description).should('exist'); + } +} diff --git a/src/pybind/mgr/dashboard/frontend/cypress/integration/ui/user-mgmt.e2e-spec.ts b/src/pybind/mgr/dashboard/frontend/cypress/integration/ui/user-mgmt.e2e-spec.ts new file mode 100644 index 000000000000..57818db0ae75 --- /dev/null +++ b/src/pybind/mgr/dashboard/frontend/cypress/integration/ui/user-mgmt.e2e-spec.ts @@ -0,0 +1,36 @@ +import { UserMgmtPageHelper } from './user-mgmt.po'; + +describe('User Management page', () => { + const userMgmt = new UserMgmtPageHelper(); + const user_name = 'e2e_user_mgmt_user'; + + beforeEach(() => { + cy.login(); + userMgmt.navigateTo(); + }); + + describe('breadcrumb tests', () => { + it('should check breadcrumb on users tab of user management page', () => { + userMgmt.expectBreadcrumbText('Users'); + }); + + it('should check breadcrumb on user creation page', () => { + userMgmt.navigateTo('create'); + userMgmt.expectBreadcrumbText('Create'); + }); + }); + + describe('user create, edit & delete test', () => { + it('should create a user', () => { + userMgmt.create(user_name, 'cool_password', 'Jeff', 'realemail@realwebsite.com'); + }); + + it('should edit a user', () => { + userMgmt.edit(user_name, 'cool_password_number_2', 'Geoff', 'w@m'); + }); + + it('should delete a user', () => { + userMgmt.delete(user_name); + }); + }); +}); diff --git a/src/pybind/mgr/dashboard/frontend/cypress/integration/ui/user-mgmt.po.ts b/src/pybind/mgr/dashboard/frontend/cypress/integration/ui/user-mgmt.po.ts new file mode 100644 index 000000000000..904cc6ed0bc1 --- /dev/null +++ b/src/pybind/mgr/dashboard/frontend/cypress/integration/ui/user-mgmt.po.ts @@ -0,0 +1,40 @@ +import { PageHelper } from '../page-helper.po'; + +export class UserMgmtPageHelper extends PageHelper { + pages = { + index: { url: '#/user-management/users', id: 'cd-user-list' }, + create: { url: '#/user-management/users/create', id: 'cd-user-form' } + }; + + create(username: string, password: string, name: string, email: string) { + this.navigateTo('create'); + + // fill in fields + cy.get('#username').type(username); + cy.get('#password').type(password); + cy.get('#confirmpassword').type(password); + cy.get('#name').type(name); + cy.get('#email').type(email); + + // Click the create button and wait for user to be made + cy.contains('button', 'Create User').click(); + this.getFirstTableCell(username).should('exist'); + } + + edit(username: string, password: string, name: string, email: string) { + this.getFirstTableCell(username).click(); // select user from table + cy.contains('button', 'Edit').click(); // click button to move to edit page + + // fill in fields with new values + cy.get('#password').clear().type(password); + cy.get('#confirmpassword').clear().type(password); + cy.get('#name').clear().type(name); + cy.get('#email').clear().type(email); + + // Click the edit button and check new values are present in table + const editButton = cy.contains('button', 'Edit User'); + editButton.click(); + this.getFirstTableCell(email).should('exist'); + this.getFirstTableCell(name).should('exist'); + } +} diff --git a/src/pybind/mgr/dashboard/frontend/cypress/support/commands.ts b/src/pybind/mgr/dashboard/frontend/cypress/support/commands.ts new file mode 100644 index 000000000000..0bcfe76ad120 --- /dev/null +++ b/src/pybind/mgr/dashboard/frontend/cypress/support/commands.ts @@ -0,0 +1,47 @@ +declare global { + namespace Cypress { + interface Chainable { + login(): void; + text(): Chainable; + } + } +} + +import { Permissions } from '../../src/app/shared/models/permissions'; + +let auth: any; + +const fillAuth = () => { + window.localStorage.setItem('dashboard_username', auth.username); + window.localStorage.setItem('access_token', auth.token); + window.localStorage.setItem('dashboard_permissions', auth.permissions); + window.localStorage.setItem('user_pwd_expiration_date', auth.pwdExpirationDate); + window.localStorage.setItem('user_pwd_update_required', auth.pwdUpdateRequired); + window.localStorage.setItem('sso', auth.sso); +}; + +Cypress.Commands.add('login', () => { + const username = Cypress.env('LOGIN_USER') || 'admin'; + const password = Cypress.env('LOGIN_PWD') || 'admin'; + + if (auth === undefined) { + cy.request({ + method: 'POST', + url: 'api/auth', + body: { username: username, password: password } + }).then((resp) => { + auth = resp.body; + auth.permissions = JSON.stringify(new Permissions(auth.permissions)); + auth.pwdExpirationDate = String(auth.pwdExpirationDate); + auth.pwdUpdateRequired = String(auth.pwdUpdateRequired); + auth.sso = String(auth.sso); + fillAuth(); + }); + } else { + fillAuth(); + } +}); + +Cypress.Commands.add('text', { prevSubject: true }, (subject) => { + return subject.text(); +}); diff --git a/src/pybind/mgr/dashboard/frontend/cypress/support/index.ts b/src/pybind/mgr/dashboard/frontend/cypress/support/index.ts new file mode 100644 index 000000000000..750acb7eafc8 --- /dev/null +++ b/src/pybind/mgr/dashboard/frontend/cypress/support/index.ts @@ -0,0 +1,5 @@ +import './commands'; + +afterEach(() => { + cy.visit('#/403'); +}); diff --git a/src/pybind/mgr/dashboard/frontend/cypress/tsconfig.json b/src/pybind/mgr/dashboard/frontend/cypress/tsconfig.json new file mode 100644 index 000000000000..681a8b35dd7d --- /dev/null +++ b/src/pybind/mgr/dashboard/frontend/cypress/tsconfig.json @@ -0,0 +1,13 @@ +{ + "extends": "../tsconfig.json", + "exclude": [], + "include": [ + "**/*.ts" + ], + "compilerOptions": { + "types": [ + "cypress" + ], + "target": "es6" + } +} diff --git a/src/pybind/mgr/dashboard/frontend/e2e/block/images.e2e-spec.ts b/src/pybind/mgr/dashboard/frontend/e2e/block/images.e2e-spec.ts deleted file mode 100644 index 1d807e790176..000000000000 --- a/src/pybind/mgr/dashboard/frontend/e2e/block/images.e2e-spec.ts +++ /dev/null @@ -1,116 +0,0 @@ -import { PoolPageHelper } from '../pools/pools.po'; -import { ImagesPageHelper } from './images.po'; - -describe('Images page', () => { - let pools: PoolPageHelper; - let images: ImagesPageHelper; - - beforeAll(() => { - images = new ImagesPageHelper(); - pools = new PoolPageHelper(); - }); - - afterEach(async () => { - await ImagesPageHelper.checkConsole(); - }); - - describe('breadcrumb and tab tests', () => { - beforeAll(async () => { - await images.navigateTo(); - }); - - it('should open and show breadcrumb', async () => { - await images.waitTextToBePresent(images.getBreadcrumb(), 'Images'); - }); - - it('should show four tabs', async () => { - await expect(images.getTabsCount()).toEqual(4); - }); - - it('should show text for all tabs', async () => { - await expect(images.getTabText(0)).toEqual('Images'); - await expect(images.getTabText(1)).toEqual('Namespaces'); - await expect(images.getTabText(2)).toEqual('Trash'); - await expect(images.getTabText(3)).toEqual('Overall Performance'); - }); - }); - - describe('create, edit & delete image test', () => { - const poolName = 'e2e_images_pool'; - const imageName = 'e2e_images#image'; - const newImageName = 'e2e_images#image_new'; - - beforeAll(async () => { - await pools.navigateTo('create'); // Need pool for image testing - await pools.create(poolName, 8, 'rbd'); - await pools.navigateTo(); - await pools.exist(poolName, true); - await images.navigateTo(); - }); - - it('should create image', async () => { - await images.createImage(imageName, poolName, '1'); - await expect(images.getFirstTableCellWithText(imageName).isPresent()).toBe(true); - }); - - it('should edit image', async () => { - await images.editImage(imageName, poolName, newImageName, '2'); - await expect(images.getFirstTableCellWithText(newImageName).isPresent()).toBe(true); - }); - - it('should delete image', async () => { - await images.navigateTo(); - await images.delete(newImageName); - }); - - afterAll(async () => { - await pools.navigateTo(); - await pools.delete(poolName); - }); - }); - - describe('move to trash, restore and purge image tests', () => { - const poolName = 'trash_pool'; - const imageName = 'trash#image'; - const newImageName = 'newtrash#image'; - - beforeAll(async () => { - await pools.navigateTo('create'); // Need pool for image testing - await pools.create(poolName, 8, 'rbd'); - await pools.navigateTo(); - await pools.exist(poolName, true); - - await images.navigateTo(); // Need image for trash testing - await images.createImage(imageName, poolName, '1'); - await expect(images.getFirstTableCellWithText(imageName).isPresent()).toBe(true); - }); - - it('should move the image to the trash', async () => { - await images.moveToTrash(imageName); - await expect(images.getFirstTableCellWithText(imageName).isPresent()).toBe(true); - }); - - it('should restore image to images table', async () => { - await images.restoreImage(imageName, newImageName); - await expect(images.getFirstTableCellWithText(newImageName).isPresent()).toBe(true); - }); - - it('should purge trash in images trash tab', async () => { - await images.navigateTo(); - // Have had issues with image not restoring fast enough, thus these tests/waits are here - await images.waitPresence( - images.getFirstTableCellWithText(newImageName), - 'Timed out waiting for image to restore' - ); - await images.moveToTrash(newImageName); - await images.purgeTrash(newImageName, poolName); - }); - - afterAll(async () => { - await pools.navigateTo(); - await pools.delete(poolName); // Deletes images test pool - await pools.navigateTo(); - await pools.exist(poolName, false); - }); - }); -}); diff --git a/src/pybind/mgr/dashboard/frontend/e2e/block/images.po.ts b/src/pybind/mgr/dashboard/frontend/e2e/block/images.po.ts deleted file mode 100644 index 11e5ab1f0e44..000000000000 --- a/src/pybind/mgr/dashboard/frontend/e2e/block/images.po.ts +++ /dev/null @@ -1,125 +0,0 @@ -import { $, $$, browser, by, element } from 'protractor'; -import { PageHelper } from '../page-helper.po'; - -export class ImagesPageHelper extends PageHelper { - pages = { - index: '/#/block/rbd', - create: '/#/block/rbd/create' - }; - - // Creates a block image and fills in the name, pool, and size fields. Then checks - // if the image is present in the Images table. - async createImage(name: string, pool: string, size: string) { - await this.navigateTo('create'); - - // Need the string '[value=""]' to find the pool in the dropdown menu - const getPoolName = `[value="${pool}"]`; - - await element(by.id('name')).sendKeys(name); // Enter in image name - - // Select image pool - await this.selectOption('pool', pool); - await $(getPoolName).click(); - await expect(element(by.id('pool')).getAttribute('class')).toContain('ng-valid'); // check if selected - - // Enter in the size of the image - await element(by.id('size')).click(); - await element(by.id('size')).sendKeys(size); - - // Click the create button and wait for image to be made - await element(by.cssContainingText('button', 'Create RBD')).click(); - return this.waitPresence(this.getFirstTableCellWithText(name)); - } - - async editImage(name: string, pool: string, newName: string, newSize: string) { - const base_url = '/#/block/rbd/edit/'; - const editURL = base_url - .concat(encodeURIComponent(pool)) - .concat('%2F') - .concat(encodeURIComponent(name)); - await browser.get(editURL); - - await element(by.id('name')).click(); // click name box and send new name - await element(by.id('name')).clear(); - await element(by.id('name')).sendKeys(newName); - await element(by.id('size')).click(); - await element(by.id('size')).clear(); - await element(by.id('size')).sendKeys(newSize); // click the size box and send new size - - await element(by.cssContainingText('button', 'Edit RBD')).click(); - await this.navigateTo(); - await this.waitClickableAndClick(this.getExpandCollapseElement(newName)); - await expect( - element.all(by.css('.table.table-striped.table-bordered')).first().getText() - ).toMatch(newSize); - } - - // Selects RBD image and moves it to the trash, checks that it is present in the - // trash table - async moveToTrash(name: string) { - await this.navigateTo(); - // wait for image to be created - await this.waitTextNotPresent($$('.datatable-body').first(), '(Creating...)'); - await this.waitClickableAndClick(this.getFirstTableCellWithText(name)); - // click on the drop down and selects the move to trash option - await $$('.table-actions button.dropdown-toggle').first().click(); - await $('li.move-to-trash').click(); - await this.waitVisibility(element(by.cssContainingText('button', 'Move Image'))); - await element(by.cssContainingText('button', 'Move Image')).click(); - await this.navigateTo(); - // Clicks trash tab - await this.waitClickableAndClick(element(by.cssContainingText('.nav-link', 'Trash'))); - await this.waitPresence(this.getFirstTableCellWithText(name)); - } - - // Checks trash tab table for image and then restores it to the RBD Images table - // (could change name if new name is given) - async restoreImage(name: string, newName?: string) { - await this.navigateTo(); - // clicks on trash tab - await element(by.cssContainingText('.nav-link', 'Trash')).click(); - // wait for table to load - await this.waitClickableAndClick(this.getFirstTableCellWithText(name)); - await element(by.cssContainingText('button', 'Restore')).click(); - // wait for pop-up to be visible (checks for title of pop-up) - await this.waitVisibility(element(by.id('name'))); - // If a new name for the image is passed, it changes the name of the image - if (newName !== undefined) { - await element(by.id('name')).click(); // click name box and send new name - await element(by.id('name')).clear(); - await element(by.id('name')).sendKeys(newName); - } - await element(by.cssContainingText('button', 'Restore Image')).click(); - await this.navigateTo(); - // clicks images tab - await element(by.cssContainingText('.nav-link', 'Images')).click(); - await this.navigateTo(); - await this.waitPresence(this.getFirstTableCellWithText(newName)); - } - - // Enters trash tab and purges trash, thus emptying the trash table. Checks if - // Image is still in the table. - async purgeTrash(name: string, pool?: string) { - await this.navigateTo(); - // clicks trash tab - await element(by.cssContainingText('.nav-link', 'Trash')).click(); - await element(by.cssContainingText('button', 'Purge Trash')).click(); - // Check for visibility of modal container - await this.waitVisibility(element(by.id('poolName'))); - // If purgeing a specific pool, selects that pool if given - if (pool !== undefined) { - const getPoolName = `[value="${pool}"]`; - await element(by.id('poolName')).click(); - await element(by.cssContainingText('select[name=poolName] option', pool)).click(); - await $(getPoolName).click(); - await expect(element(by.id('poolName')).getAttribute('class')).toContain('ng-valid'); // check if pool is selected - } - await this.waitClickableAndClick(element(by.id('purgeFormButton'))); - // Wait for image to delete and check it is not present - await this.waitStaleness( - this.getFirstTableCellWithText(name), - 'Timed out waiting for image to be purged' - ); - await expect(this.getFirstTableCellWithText(name).isPresent()).toBe(false); - } -} diff --git a/src/pybind/mgr/dashboard/frontend/e2e/block/iscsi.e2e-spec.ts b/src/pybind/mgr/dashboard/frontend/e2e/block/iscsi.e2e-spec.ts deleted file mode 100644 index 79ea41db8480..000000000000 --- a/src/pybind/mgr/dashboard/frontend/e2e/block/iscsi.e2e-spec.ts +++ /dev/null @@ -1,41 +0,0 @@ -import { IscsiPageHelper } from './iscsi.po'; - -describe('Iscsi Page', () => { - let iscsi: IscsiPageHelper; - - beforeAll(() => { - iscsi = new IscsiPageHelper(); - }); - - afterEach(async () => { - await IscsiPageHelper.checkConsole(); - }); - - describe('breadcrumb test', () => { - beforeAll(async () => { - await iscsi.navigateTo(); - }); - - it('should open and show breadcrumb', async () => { - await iscsi.waitTextToBePresent(iscsi.getBreadcrumb(), 'Overview'); - }); - }); - - describe('fields check', () => { - beforeAll(async () => { - await iscsi.navigateTo(); - }); - - it('should check that tables are displayed and legends are correct', async () => { - // Check tables are displayed - const dataTables = iscsi.getDataTables(); - await expect(dataTables.get(0).isDisplayed()); - await expect(dataTables.get(1).isDisplayed()); - - // Check that legends are correct - const legends = iscsi.getLegends(); - await expect(legends.get(0).getText()).toMatch('Gateways'); - await expect(legends.get(1).getText()).toMatch('Images'); - }); - }); -}); diff --git a/src/pybind/mgr/dashboard/frontend/e2e/block/iscsi.po.ts b/src/pybind/mgr/dashboard/frontend/e2e/block/iscsi.po.ts deleted file mode 100644 index e2621f0451aa..000000000000 --- a/src/pybind/mgr/dashboard/frontend/e2e/block/iscsi.po.ts +++ /dev/null @@ -1,5 +0,0 @@ -import { PageHelper } from '../page-helper.po'; - -export class IscsiPageHelper extends PageHelper { - pages = { index: '/#/block/iscsi/overview' }; -} diff --git a/src/pybind/mgr/dashboard/frontend/e2e/block/mirroring.e2e-spec.ts b/src/pybind/mgr/dashboard/frontend/e2e/block/mirroring.e2e-spec.ts deleted file mode 100644 index 3327cf161bbb..000000000000 --- a/src/pybind/mgr/dashboard/frontend/e2e/block/mirroring.e2e-spec.ts +++ /dev/null @@ -1,63 +0,0 @@ -import { PoolPageHelper } from '../pools/pools.po'; -import { MirroringPageHelper } from './mirroring.po'; - -describe('Mirroring page', () => { - let pools: PoolPageHelper; - let mirroring: MirroringPageHelper; - - beforeAll(() => { - mirroring = new MirroringPageHelper(); - pools = new PoolPageHelper(); - }); - - afterEach(async () => { - await MirroringPageHelper.checkConsole(); - }); - - describe('breadcrumb and tab tests', () => { - beforeAll(async () => { - await mirroring.navigateTo(); - }); - - it('should open and show breadcrumb', async () => { - await mirroring.waitTextToBePresent(mirroring.getBreadcrumb(), 'Mirroring'); - }); - - it('should show three tabs', async () => { - await expect(mirroring.getTabsCount()).toEqual(3); - }); - - it('should show text for all tabs', async () => { - await expect(mirroring.getTabText(0)).toEqual('Issues'); - await expect(mirroring.getTabText(1)).toEqual('Syncing'); - await expect(mirroring.getTabText(2)).toEqual('Ready'); - }); - }); - - describe('checks that edit mode functionality shows in the pools table', () => { - const poolName = 'mirroring_test'; - - beforeAll(async () => { - await pools.navigateTo('create'); // Need pool for mirroring testing - await pools.create(poolName, 8, 'rbd'); - await pools.navigateTo(); - await pools.exist(poolName, true); - }); - - it('tests editing mode for pools', async () => { - await mirroring.navigateTo(); - - await mirroring.editMirror(poolName, 'Pool'); - await expect(mirroring.getFirstTableCellWithText('pool').isPresent()).toBe(true); - await mirroring.editMirror(poolName, 'Image'); - await expect(mirroring.getFirstTableCellWithText('image').isPresent()).toBe(true); - await mirroring.editMirror(poolName, 'Disabled'); - await expect(mirroring.getFirstTableCellWithText('disabled').isPresent()).toBe(true); - }); - - afterAll(async () => { - await pools.navigateTo(); - await pools.delete(poolName); - }); - }); -}); diff --git a/src/pybind/mgr/dashboard/frontend/e2e/block/mirroring.po.ts b/src/pybind/mgr/dashboard/frontend/e2e/block/mirroring.po.ts deleted file mode 100644 index d27a1dc6e3af..000000000000 --- a/src/pybind/mgr/dashboard/frontend/e2e/block/mirroring.po.ts +++ /dev/null @@ -1,33 +0,0 @@ -import { $, by, element } from 'protractor'; -import { PageHelper } from '../page-helper.po'; - -const pages = { index: '/#/block/mirroring' }; - -export class MirroringPageHelper extends PageHelper { - pages = pages; - - /** - * Goes to the mirroring page and edits a pool in the Pool table. Clicks on the - * pool and chooses an option (either pool, image, or disabled) - */ - @PageHelper.restrictTo(pages.index) - async editMirror(name: string, option: string) { - // Clicks the pool in the table - await this.waitClickableAndClick(this.getFirstTableCellWithText(name)); - - // Clicks the Edit Mode button - const editModeButton = element(by.cssContainingText('button', 'Edit Mode')); - await this.waitClickableAndClick(editModeButton); - // Clicks the drop down in the edit pop-up, then clicks the Update button - await this.waitVisibility($('.modal-content')); - await this.selectOption('mirrorMode', option); - - // Clicks update button and checks if the mode has been changed - await element(by.cssContainingText('button', 'Update')).click(); - await this.waitStaleness( - element(by.cssContainingText('.modal-dialog', 'Edit pool mirror mode')) - ); - const val = option.toLowerCase(); // used since entries in table are lower case - await this.waitVisibility(this.getFirstTableCellWithText(val)); - } -} diff --git a/src/pybind/mgr/dashboard/frontend/e2e/cluster/configuration.e2e-spec.ts b/src/pybind/mgr/dashboard/frontend/e2e/cluster/configuration.e2e-spec.ts deleted file mode 100644 index 57b4d921a459..000000000000 --- a/src/pybind/mgr/dashboard/frontend/e2e/cluster/configuration.e2e-spec.ts +++ /dev/null @@ -1,80 +0,0 @@ -import { $ } from 'protractor'; -import { ConfigurationPageHelper } from './configuration.po'; - -describe('Configuration page', () => { - let configuration: ConfigurationPageHelper; - - beforeAll(() => { - configuration = new ConfigurationPageHelper(); - }); - - afterEach(async () => { - await ConfigurationPageHelper.checkConsole(); - }); - - describe('breadcrumb test', () => { - beforeAll(async () => { - await configuration.navigateTo(); - }); - - it('should open and show breadcrumb', async () => { - await configuration.waitTextToBePresent(configuration.getBreadcrumb(), 'Configuration'); - }); - }); - - describe('fields check', () => { - beforeAll(async () => { - await configuration.navigateTo(); - await configuration.waitClickableAndClick(configuration.getFirstExpandCollapseElement()); - }); - - it('should verify that selected footer increases when an entry is clicked', async () => { - const selectedCount = await configuration.getTableSelectedCount(); - await expect(selectedCount).toBe(1); - }); - - it('should check that details table opens and tab is correct', async () => { - await expect($('.table.table-striped.table-bordered').isDisplayed()); - await expect(configuration.getTabsCount()).toEqual(1); - await expect(configuration.getTabText(0)).toEqual('Details'); - }); - }); - - describe('edit configuration test', () => { - const configName = 'client_cache_size'; - - beforeAll(async () => { - await configuration.navigateTo(); - }); - - beforeEach(async () => { - await configuration.clearTableSearchInput(); - }); - - afterAll(async () => { - await configuration.configClear(configName); - }); - - it('should click and edit a configuration and results should appear in the table', async () => { - await configuration.edit( - configName, - ['global', '1'], - ['mon', '2'], - ['mgr', '3'], - ['osd', '4'], - ['mds', '5'], - ['client', '6'] - ); - }); - - it('should show only modified configurations', async () => { - await configuration.filterTable('Modified', 'yes'); - expect(await configuration.getTableFoundCount()).toBe(1); - }); - - it('should hide all modified configurations', async () => { - await configuration.filterTable('Modified', 'no'); - expect(await configuration.getTableFoundCount()).toBeGreaterThan(1); - }); - }); -}); diff --git a/src/pybind/mgr/dashboard/frontend/e2e/cluster/configuration.po.ts b/src/pybind/mgr/dashboard/frontend/e2e/cluster/configuration.po.ts deleted file mode 100644 index 635522653996..000000000000 --- a/src/pybind/mgr/dashboard/frontend/e2e/cluster/configuration.po.ts +++ /dev/null @@ -1,93 +0,0 @@ -import { $, by, element, protractor } from 'protractor'; -import { PageHelper } from '../page-helper.po'; - -export class ConfigurationPageHelper extends PageHelper { - pages = { - index: '/#/configuration' - }; - - async configClear(name: string) { - // Clears out all the values in a config to reset before and after testing - // Does not work for configs with checkbox only, possible future PR - - await this.navigateTo(); - const valList = ['global', 'mon', 'mgr', 'osd', 'mds', 'client']; // Editable values - - // Enter config setting name into filter box - await $('input.form-control.ng-valid').clear(); - await $('input.form-control.ng-valid').sendKeys(name); - - // Selects config that we want to clear - await this.waitClickableAndClick(this.getFirstTableCellWithText(name)); // waits for config to be clickable and click - await element(by.cssContainingText('button', 'Edit')).click(); // clicks button to edit - - for (const i of valList) { - // Sends two backspaces to all values, clear() did not work in this instance, could be optimized more - await element(by.id(i)).sendKeys(protractor.Key.chord(protractor.Key.CONTROL, 'a')); - await element(by.id(i)).sendKeys(protractor.Key.BACK_SPACE); - } - // Clicks save button and checks that values are not present for the selected config - await element(by.cssContainingText('button', 'Save')).click(); - - // Enter config setting name into filter box - await $('input.form-control.ng-valid').clear(); - await $('input.form-control.ng-valid').sendKeys(name); - - // Expand row - await this.waitClickableAndClick(this.getExpandCollapseElement(name)); - // Clicks desired config - await this.waitVisibility( - $('.table.table-striped.table-bordered'), // Checks for visibility of details tab - 'config details did not appear' - ); - for (const i of valList) { - // Waits until values are not present in the details table - await this.waitTextNotPresent($('.table.table-striped.table-bordered'), i + ':'); - } - } - - async edit(name: string, ...values: [string, string][]) { - // Clicks the designated config, then inputs the values passed into the edit function. - // Then checks if the edit is reflected in the config table. Takes in name of config and - // a list of tuples of values the user wants edited, each tuple having the desired value along - // with the number tehey want for that value. Ex: [global, '2'] is the global value with an input of 2 - await this.navigateTo(); - - // Enter config setting name into filter box - await $('input.form-control.ng-valid').clear(); - await $('input.form-control.ng-valid').sendKeys(name); - - // Selects config that we want to edit - await this.waitClickableAndClick(this.getFirstTableCellWithText(name)); // waits for config to be clickable and click - await element(by.cssContainingText('button', 'Edit')).click(); // clicks button to edit - - await this.waitTextToBePresent(this.getBreadcrumb(), 'Edit'); - - for (let i = 0, valtuple; (valtuple = values[i]); i++) { - // Finds desired value based off given list - await element(by.id(valtuple[0])).sendKeys(valtuple[1]); // of values and inserts the given number for the value - } - - // Clicks save button then waits until the desired config is visible, clicks it, then checks - // that each desired value appears with the desired number - await element(by.cssContainingText('button', 'Save')).click(); - await this.navigateTo(); - - // Enter config setting name into filter box - await $('input.form-control.ng-valid').clear(); - await $('input.form-control.ng-valid').sendKeys(name); - - await this.waitVisibility(this.getFirstTableCellWithText(name)); - // Checks for visibility of config in table - await this.getExpandCollapseElement(name).click(); - // Clicks config - for (let i = 0, valtuple; (valtuple = values[i]); i++) { - // iterates through list of values and - await this.waitTextToBePresent( - // checks if the value appears in details with the correct number attatched - $('.table.table-striped.table-bordered'), - valtuple[0] + ': ' + valtuple[1] - ); - } - } -} diff --git a/src/pybind/mgr/dashboard/frontend/e2e/cluster/crush-map.e2e-spec.ts b/src/pybind/mgr/dashboard/frontend/e2e/cluster/crush-map.e2e-spec.ts deleted file mode 100644 index 07687bdc15dc..000000000000 --- a/src/pybind/mgr/dashboard/frontend/e2e/cluster/crush-map.e2e-spec.ts +++ /dev/null @@ -1,44 +0,0 @@ -import { $ } from 'protractor'; -import { CrushMapPageHelper } from './crush-map.po'; - -describe('CRUSH map page', () => { - let crushmap: CrushMapPageHelper; - - beforeAll(() => { - crushmap = new CrushMapPageHelper(); - }); - - afterEach(async () => { - await CrushMapPageHelper.checkConsole(); - }); - - describe('breadcrumb test', () => { - beforeAll(async () => { - await crushmap.navigateTo(); - }); - - it('should open and show breadcrumb', async () => { - await crushmap.waitTextToBePresent(crushmap.getBreadcrumb(), 'CRUSH map'); - }); - }); - describe('fields check', () => { - beforeAll(async () => { - await crushmap.navigateTo(); - }); - - it('should check that title & table appears', async () => { - // Check that title (CRUSH map viewer) appears - await expect(crushmap.getPageTitle()).toMatch('CRUSH map viewer'); - - // Check that title appears once OSD is clicked - await crushmap.getCrushNode(1).click(); - - const label = await $('legend').getText(); // Get table label - await expect(crushmap.getCrushNode(1).getText()).toEqual(label); - - // Check that table appears once OSD is clicked - await crushmap.waitVisibility($('.datatable-body')); - await expect($('.datatable-body').isDisplayed()).toBe(true); - }); - }); -}); diff --git a/src/pybind/mgr/dashboard/frontend/e2e/cluster/crush-map.po.ts b/src/pybind/mgr/dashboard/frontend/e2e/cluster/crush-map.po.ts deleted file mode 100644 index 20790fa3fe60..000000000000 --- a/src/pybind/mgr/dashboard/frontend/e2e/cluster/crush-map.po.ts +++ /dev/null @@ -1,14 +0,0 @@ -import { $, $$ } from 'protractor'; -import { PageHelper } from '../page-helper.po'; - -export class CrushMapPageHelper extends PageHelper { - pages = { index: '/#/crush-map' }; - - getPageTitle() { - return $('cd-crushmap .card-header').getText(); - } - - getCrushNode(idx: number) { - return $$('.node-name.type-osd').get(idx); - } -} diff --git a/src/pybind/mgr/dashboard/frontend/e2e/cluster/hosts.e2e-spec.ts b/src/pybind/mgr/dashboard/frontend/e2e/cluster/hosts.e2e-spec.ts deleted file mode 100644 index 1b64cdbfa83b..000000000000 --- a/src/pybind/mgr/dashboard/frontend/e2e/cluster/hosts.e2e-spec.ts +++ /dev/null @@ -1,45 +0,0 @@ -import { HostsPageHelper } from './hosts.po'; - -describe('Hosts page', () => { - let hosts: HostsPageHelper; - - beforeAll(() => { - hosts = new HostsPageHelper(); - }); - - afterEach(async () => { - await HostsPageHelper.checkConsole(); - }); - - describe('breadcrumb and tab tests', () => { - beforeAll(async () => { - await hosts.navigateTo(); - }); - - it('should open and show breadcrumb', async () => { - await hosts.waitTextToBePresent(hosts.getBreadcrumb(), 'Hosts'); - }); - - it('should show two tabs', async () => { - await expect(hosts.getTabsCount()).toEqual(2); - }); - - it('should show hosts list tab at first', async () => { - await expect(hosts.getTabText(0)).toEqual('Hosts List'); - }); - - it('should show overall performance as a second tab', async () => { - await expect(hosts.getTabText(1)).toEqual('Overall Performance'); - }); - }); - - describe('services link test', () => { - it('should check at least one host is present', async () => { - await hosts.check_for_host(); - }); - - it('should check services link(s) work for first host', async () => { - await hosts.check_services_links(); - }); - }); -}); diff --git a/src/pybind/mgr/dashboard/frontend/e2e/cluster/hosts.po.ts b/src/pybind/mgr/dashboard/frontend/e2e/cluster/hosts.po.ts deleted file mode 100644 index df935c6967ad..000000000000 --- a/src/pybind/mgr/dashboard/frontend/e2e/cluster/hosts.po.ts +++ /dev/null @@ -1,46 +0,0 @@ -import { by, element } from 'protractor'; -import { PageHelper } from '../page-helper.po'; - -export class HostsPageHelper extends PageHelper { - pages = { index: '/#/hosts' }; - - async check_for_host() { - await this.navigateTo(); - - await expect(this.getTableTotalCount()).not.toBe(0); - } - - // function that checks all services links work for first - // host in table - async check_services_links() { - await this.navigateTo(); - let links_tested = 0; - - const services = element.all(by.css('cd-hosts a.service-link')); - // check that text (links) is present in services box - await expect(services.count()).toBeGreaterThan(0, 'No services links exist on first host'); - - /** - * Currently there is an issue [1] in ceph that it's causing - * a random appearance of a mds service in the hosts service listing. - * Decreasing the number of service by 1 temporarily fixes the e2e failure. - * - * TODO: Revert this change when the issue has been fixed. - * - * [1] https://tracker.ceph.com/issues/41538 - */ - const num_links = (await services.count()) - 1; - - for (let i = 0; i < num_links; i++) { - // click link, check it worked by looking for changed breadcrumb, - // navigate back to hosts page, repeat until all links checked - await services.get(i).click(); - await this.waitTextToBePresent(this.getBreadcrumb(), 'Performance Counters'); - await this.navigateBack(); - await this.waitTextToBePresent(this.getBreadcrumb(), 'Hosts'); - links_tested++; - } - // check if any links were actually tested - await expect(links_tested > 0).toBe(true, 'No links were tested. Test failed'); - } -} diff --git a/src/pybind/mgr/dashboard/frontend/e2e/cluster/logs.e2e-spec.ts b/src/pybind/mgr/dashboard/frontend/e2e/cluster/logs.e2e-spec.ts deleted file mode 100644 index 47c95a39bdcf..000000000000 --- a/src/pybind/mgr/dashboard/frontend/e2e/cluster/logs.e2e-spec.ts +++ /dev/null @@ -1,83 +0,0 @@ -import { PoolPageHelper } from '../pools/pools.po'; -import { ConfigurationPageHelper } from './configuration.po'; -import { LogsPageHelper } from './logs.po'; - -describe('Logs page', () => { - let logs: LogsPageHelper; - let pools: PoolPageHelper; - let configuration: ConfigurationPageHelper; - - const poolname = 'logs_e2e_test_pool'; - const configname = 'log_graylog_port'; - const today = new Date(); - let hour = today.getHours(); - if (hour > 12) { - hour = hour - 12; - } - const minute = today.getMinutes(); - - beforeAll(() => { - logs = new LogsPageHelper(); - pools = new PoolPageHelper(); - configuration = new ConfigurationPageHelper(); - }); - - afterEach(async () => { - await LogsPageHelper.checkConsole(); - }); - - describe('breadcrumb and tab tests', () => { - beforeAll(async () => { - await logs.navigateTo(); - }); - - it('should open and show breadcrumb', async () => { - await logs.waitTextToBePresent(logs.getBreadcrumb(), 'Logs'); - }); - - it('should show two tabs', async () => { - await expect(logs.getTabsCount()).toEqual(2); - }); - - it('should show cluster logs tab at first', async () => { - await expect(logs.getTabText(0)).toEqual('Cluster Logs'); - }); - - it('should show audit logs as a second tab', async () => { - await expect(logs.getTabText(1)).toEqual('Audit Logs'); - }); - }); - - describe('audit logs respond to pool creation and deletion test', () => { - it('should create pool and check audit logs reacted', async () => { - await pools.navigateTo('create'); - await pools.create(poolname, 8); - - await pools.navigateTo(); - await pools.exist(poolname, true); - - await logs.checkAuditForPoolFunction(poolname, 'create', hour, minute); - }); - - it('should delete pool and check audit logs reacted', async () => { - await pools.navigateTo(); - await pools.delete(poolname); - - await logs.navigateTo(); - await logs.checkAuditForPoolFunction(poolname, 'delete', hour, minute); - }); - }); - - describe('audit logs respond to editing configuration setting test', () => { - it('should change config settings and check audit logs reacted', async () => { - await configuration.navigateTo(); - await configuration.edit(configname, ['global', '5']); - - await logs.navigateTo(); - await logs.checkAuditForConfigChange(configname, 'global', hour, minute); - - await configuration.navigateTo(); - await configuration.configClear(configname); - }); - }); -}); diff --git a/src/pybind/mgr/dashboard/frontend/e2e/cluster/logs.po.ts b/src/pybind/mgr/dashboard/frontend/e2e/cluster/logs.po.ts deleted file mode 100644 index 7cc87f822f24..000000000000 --- a/src/pybind/mgr/dashboard/frontend/e2e/cluster/logs.po.ts +++ /dev/null @@ -1,98 +0,0 @@ -import { $, $$, by, element, protractor } from 'protractor'; -import { PageHelper } from '../page-helper.po'; - -export class LogsPageHelper extends PageHelper { - pages = { index: '/#/logs' }; - - async checkAuditForPoolFunction( - poolname: string, - poolfunction: string, - hour: number, - minute: number - ) { - await this.navigateTo(); - - // sometimes the modal from deleting pool is still present at this point. - // This wait makes sure it isn't - await this.waitStaleness(element(by.cssContainingText('.modal-dialog', 'Delete Pool'))); - - // go to audit logs tab - await element(by.cssContainingText('.nav-link', 'Audit Logs')).click(); - - // Enter an earliest time so that no old messages with the same pool name show up - await $$('.bs-timepicker-field') - .get(0) - .sendKeys(protractor.Key.chord(protractor.Key.CONTROL, 'a')); - await $$('.bs-timepicker-field').get(0).sendKeys(protractor.Key.BACK_SPACE); - if (hour < 10) { - await $$('.bs-timepicker-field').get(0).sendKeys('0'); - } - await $$('.bs-timepicker-field').get(0).sendKeys(hour); - - await $$('.bs-timepicker-field') - .get(1) - .sendKeys(protractor.Key.chord(protractor.Key.CONTROL, 'a')); - await $$('.bs-timepicker-field').get(1).sendKeys(protractor.Key.BACK_SPACE); - if (minute < 10) { - await $$('.bs-timepicker-field').get(1).sendKeys('0'); - } - await $$('.bs-timepicker-field').get(1).sendKeys(minute); - - // Enter the pool name into the filter box - await $$('input.form-control.ng-valid').first().click(); - await $$('input.form-control.ng-valid').first().clear(); - await $$('input.form-control.ng-valid').first().sendKeys(poolname); - - const audit_logs_tab = $('.tab-pane.active'); - const audit_logs_body = audit_logs_tab.element(by.css('.card-body')); - const logs = audit_logs_body.all(by.cssContainingText('.message', poolname)); - - await expect(logs.getText()).toMatch(poolname); - await expect(logs.getText()).toMatch(`pool ${poolfunction}`); - } - - async checkAuditForConfigChange( - configname: string, - setting: string, - hour: number, - minute: number - ) { - await this.navigateTo(); - - // go to audit logs tab - await element(by.cssContainingText('.nav-link', 'Audit Logs')).click(); - - // Enter an earliest time so that no old messages with the same config name show up - await $$('.bs-timepicker-field') - .get(0) - .sendKeys(protractor.Key.chord(protractor.Key.CONTROL, 'a')); - await $$('.bs-timepicker-field').get(0).sendKeys(protractor.Key.BACK_SPACE); - if (hour < 10) { - await $$('.bs-timepicker-field').get(0).sendKeys('0'); - } - await $$('.bs-timepicker-field').get(0).sendKeys(hour); - - await $$('.bs-timepicker-field') - .get(1) - .sendKeys(protractor.Key.chord(protractor.Key.CONTROL, 'a')); - await $$('.bs-timepicker-field').get(1).sendKeys(protractor.Key.BACK_SPACE); - if (minute < 10) { - await $$('.bs-timepicker-field').get(1).sendKeys('0'); - } - await $$('.bs-timepicker-field').get(1).sendKeys(minute); - - // Enter the config name into the filter box - await $$('input.form-control.ng-valid').first().click(); - await $$('input.form-control.ng-valid').first().clear(); - await $$('input.form-control.ng-valid').first().sendKeys(configname); - - const audit_logs_tab = $('.tab-pane.active'); - const audit_logs_body = audit_logs_tab.element(by.css('.card-body')); - const logs = audit_logs_body.all(by.cssContainingText('.message', configname)); - - await this.waitPresence(logs.first()); - - await expect(logs.getText()).toMatch(configname); - await expect(logs.getText()).toMatch(setting); - } -} diff --git a/src/pybind/mgr/dashboard/frontend/e2e/cluster/mgr-modules.e2e-spec.ts b/src/pybind/mgr/dashboard/frontend/e2e/cluster/mgr-modules.e2e-spec.ts deleted file mode 100644 index d14f35a4514e..000000000000 --- a/src/pybind/mgr/dashboard/frontend/e2e/cluster/mgr-modules.e2e-spec.ts +++ /dev/null @@ -1,54 +0,0 @@ -import { ManagerModulesPageHelper } from './mgr-modules.po'; - -describe('Manager modules page', () => { - let mgrmodules: ManagerModulesPageHelper; - - beforeAll(() => { - mgrmodules = new ManagerModulesPageHelper(); - }); - - afterEach(async () => { - await ManagerModulesPageHelper.checkConsole(); - }); - - describe('breadcrumb test', () => { - beforeAll(async () => { - await mgrmodules.navigateTo(); - }); - - it('should open and show breadcrumb', async () => { - await mgrmodules.waitTextToBePresent(mgrmodules.getBreadcrumb(), 'Manager modules'); - }); - }); - - describe('verifies editing functionality for manager modules', () => { - beforeAll(async () => { - await mgrmodules.navigateTo(); - }); - - it('should test editing on diskprediction_local module', async () => { - const diskpredLocalArr = [ - ['11', 'predict_interval'], - ['0122', 'sleep_interval'] - ]; - await mgrmodules.editMgrModule('diskprediction_local', diskpredLocalArr); - }); - - it('should test editing on balancer module', async () => { - const balancerArr = [['rq', 'pool_ids']]; - await mgrmodules.editMgrModule('balancer', balancerArr); - }); - - it('should test editing on dashboard module', async () => { - const dashboardArr = [ - ['rq', 'RGW_API_USER_ID'], - ['rafa', 'GRAFANA_API_PASSWORD'] - ]; - await mgrmodules.editMgrModule('dashboard', dashboardArr); - }); - - it('should test editing on devicehealth module', async () => { - await mgrmodules.editDevicehealth('1987', 'sox', '1999', '2020', '456', '567'); - }); - }); -}); diff --git a/src/pybind/mgr/dashboard/frontend/e2e/cluster/mgr-modules.po.ts b/src/pybind/mgr/dashboard/frontend/e2e/cluster/mgr-modules.po.ts deleted file mode 100644 index 1b928c45505a..000000000000 --- a/src/pybind/mgr/dashboard/frontend/e2e/cluster/mgr-modules.po.ts +++ /dev/null @@ -1,145 +0,0 @@ -import { $$, by, element } from 'protractor'; -import { PageHelper } from '../page-helper.po'; - -export class ManagerModulesPageHelper extends PageHelper { - pages = { - index: '/#/mgr-modules' - }; - - // NOTABLE ISSUES: .clear() does not work on most text boxes, therefore using sendKeys - // a Ctrl + 'a' BACK_SPACE is used. - // The need to click the module repeatedly in the table is to ensure - // that the values in the details tab updated. This fixed a bug I experienced. - - async editMgrModule(name: string, tuple: string[][]) { - // Selects the Manager Module and then fills in the desired fields. - // Doesn't check/uncheck boxes because it is not reflected in the details table. - // DOES NOT WORK FOR ALL MGR MODULES, for example, Device health - await this.navigateTo(); - await this.waitClickableAndClick(this.getFirstTableCellWithText(name)); - await element(by.cssContainingText('button', 'Edit')).click(); - - for (const entry of tuple) { - // Clears fields and adds edits - await this.clearInput(element(by.id(entry[1]))); - await element(by.id(entry[1])).sendKeys(entry[0]); - } - - await element(by.cssContainingText('button', 'Update')).click(); - // Checks if edits appear - await this.navigateTo(); - await this.waitVisibility(this.getFirstTableCellWithText(name)); - await this.getExpandCollapseElement(name).click(); - for (const entry of tuple) { - await this.waitTextToBePresent($$('.datatable-body').last(), entry[0]); - } - - // Clear mgr module of all edits made to it - await this.navigateTo(); - await this.waitClickableAndClick(this.getFirstTableCellWithText(name)); - await element(by.cssContainingText('button', 'Edit')).click(); - - // Clears the editable fields - for (const entry of tuple) { - await this.clearInput(element(by.id(entry[1]))); - } - - // Checks that clearing represents in details tab of module - await element(by.cssContainingText('button', 'Update')).click(); - await this.navigateTo(); - await this.waitVisibility(this.getFirstTableCellWithText(name)); - await this.getExpandCollapseElement(name).click(); - for (const entry of tuple) { - await this.waitTextNotPresent($$('.datatable-body').last(), entry[0]); - } - } - - async editDevicehealth( - threshhold?: string, - pooln?: string, - retention?: string, - scrape?: string, - sleep?: string, - warn?: string - ) { - // Isn't called by editMgrModule since clearing doesn't work. - // Selects the Devicehealth manager module, then fills in the desired fields, including all fields except - // checkboxes. Clicking checkboxes has been a notable issue in Protractor, therefore they were omitted in this - // version of the tests. Could be added in a future PR. Then checks if these edits appear in the details table. - await this.navigateTo(); - let devHealthArray: [string, string][]; - devHealthArray = [ - [threshhold, 'mark_out_threshold'], - [pooln, 'pool_name'], - [retention, 'retention_period'], - [scrape, 'scrape_frequency'], - [sleep, 'sleep_interval'], - [warn, 'warn_threshold'] - ]; - - await this.waitClickableAndClick(this.getFirstTableCellWithText('devicehealth')); - await element(by.cssContainingText('button', 'Edit')).click(); - for (let i = 0, devHealthTuple; (devHealthTuple = devHealthArray[i]); i++) { - if (devHealthTuple[0] !== undefined) { - // Clears and inputs edits - await this.clearInput(element(by.id(devHealthTuple[1]))); - await element(by.id(devHealthTuple[1])).sendKeys(devHealthTuple[0]); - } - } - - await element(by.cssContainingText('button', 'Update')).click(); - await this.navigateTo(); - await this.waitVisibility(this.getFirstTableCellWithText('devicehealth')); - // Checks for visibility of devicehealth in table - await this.getExpandCollapseElement('devicehealth').click(); - for (let i = 0, devHealthTuple: [string, string]; (devHealthTuple = devHealthArray[i]); i++) { - if (devHealthTuple[0] !== undefined) { - await this.waitFn(async () => { - // Repeatedly reclicks the module to check if edits has been done - await element(by.cssContainingText('.datatable-body-cell-label', 'devicehealth')).click(); - return this.waitTextToBePresent($$('.datatable-body').last(), devHealthTuple[0]); - }); - } - } - - // Inputs old values into devicehealth fields. This manager module doesnt allow for updates - // to be made when the values are cleared. Therefore, I restored them to their original values - // (on my local run of ceph-dev, this is subject to change i would assume). I'd imagine there is a - // better way of doing this. - await this.navigateTo(); - await this.waitClickableAndClick(this.getFirstTableCellWithText('devicehealth')); - await element(by.cssContainingText('button', 'Edit')).click(); - await this.clearInput(element(by.id('mark_out_threshold'))); - await element(by.id('mark_out_threshold')).sendKeys('2419200'); - - await this.clearInput(element(by.id('pool_name'))); - await element(by.id('pool_name')).sendKeys('device_health_metrics'); - - await this.clearInput(element(by.id('retention_period'))); - await element(by.id('retention_period')).sendKeys('15552000'); - - await this.clearInput(element(by.id('scrape_frequency'))); - await element(by.id('scrape_frequency')).sendKeys('86400'); - - await this.clearInput(element(by.id('sleep_interval'))); - await element(by.id('sleep_interval')).sendKeys('600'); - - await this.clearInput(element(by.id('warn_threshold'))); - await element(by.id('warn_threshold')).sendKeys('7257600'); - - // Checks that clearing represents in details tab - await this.waitClickableAndClick(element(by.cssContainingText('button', 'Update'))); - await this.navigateTo(); - await this.waitVisibility(this.getFirstTableCellWithText('devicehealth')); - await this.getExpandCollapseElement('devicehealth').click(); - for (let i = 0, devHealthTuple: [string, string]; (devHealthTuple = devHealthArray[i]); i++) { - if (devHealthTuple[0] !== undefined) { - await this.waitFn(async () => { - // Repeatedly reclicks the module to check if clearing has been done - await element(by.cssContainingText('.datatable-body-cell-label', 'devicehealth')).click(); - return this.waitTextNotPresent($$('.datatable-body').last(), devHealthTuple[0]); - }); - } - } - } -} diff --git a/src/pybind/mgr/dashboard/frontend/e2e/cluster/monitors.e2e-spec.ts b/src/pybind/mgr/dashboard/frontend/e2e/cluster/monitors.e2e-spec.ts deleted file mode 100644 index f5524b6e6818..000000000000 --- a/src/pybind/mgr/dashboard/frontend/e2e/cluster/monitors.e2e-spec.ts +++ /dev/null @@ -1,65 +0,0 @@ -import { MonitorsPageHelper } from './monitors.po'; - -describe('Monitors page', () => { - let monitors: MonitorsPageHelper; - - beforeAll(() => { - monitors = new MonitorsPageHelper(); - }); - - afterEach(async () => { - await MonitorsPageHelper.checkConsole(); - }); - - describe('breadcrumb test', () => { - beforeAll(async () => { - await monitors.navigateTo(); - }); - - it('should open and show breadcrumb', async () => { - await monitors.waitTextToBePresent(monitors.getBreadcrumb(), 'Monitors'); - }); - }); - - describe('fields check', () => { - beforeAll(async () => { - await monitors.navigateTo(); - }); - - it('should check status table is present', async () => { - // check for table header 'Status' - await expect(monitors.getLegends().get(0).getText()).toMatch('Status'); - - // check for fields in table - await expect(monitors.getStatusTables().getText()).toMatch('Cluster ID'); - await expect(monitors.getStatusTables().getText()).toMatch('monmap modified'); - await expect(monitors.getStatusTables().getText()).toMatch('monmap epoch'); - await expect(monitors.getStatusTables().getText()).toMatch('quorum con'); - await expect(monitors.getStatusTables().getText()).toMatch('quorum mon'); - await expect(monitors.getStatusTables().getText()).toMatch('required con'); - await expect(monitors.getStatusTables().getText()).toMatch('required mon'); - }); - - it('should check In Quorum and Not In Quorum tables are present', async () => { - // check for there to be two tables - await expect(monitors.getDataTables().count()).toEqual(2); - - // check for table header 'In Quorum' - await expect(monitors.getLegends().get(1).getText()).toMatch('In Quorum'); - - // check for table header 'Not In Quorum' - await expect(monitors.getLegends().get(2).getText()).toMatch('Not In Quorum'); - - // verify correct columns on In Quorum table - await expect(monitors.getDataTableHeaders().get(0).getText()).toMatch('Name'); - await expect(monitors.getDataTableHeaders().get(0).getText()).toMatch('Rank'); - await expect(monitors.getDataTableHeaders().get(0).getText()).toMatch('Public Address'); - await expect(monitors.getDataTableHeaders().get(0).getText()).toMatch('Open Sessions'); - - // verify correct columns on Not In Quorum table - await expect(monitors.getDataTableHeaders().get(1).getText()).toMatch('Name'); - await expect(monitors.getDataTableHeaders().get(1).getText()).toMatch('Rank'); - await expect(monitors.getDataTableHeaders().get(1).getText()).toMatch('Public Address'); - }); - }); -}); diff --git a/src/pybind/mgr/dashboard/frontend/e2e/cluster/monitors.po.ts b/src/pybind/mgr/dashboard/frontend/e2e/cluster/monitors.po.ts deleted file mode 100644 index c935eba1e860..000000000000 --- a/src/pybind/mgr/dashboard/frontend/e2e/cluster/monitors.po.ts +++ /dev/null @@ -1,5 +0,0 @@ -import { PageHelper } from '../page-helper.po'; - -export class MonitorsPageHelper extends PageHelper { - pages = { index: '/#/monitor' }; -} diff --git a/src/pybind/mgr/dashboard/frontend/e2e/cluster/osds.e2e-spec.ts b/src/pybind/mgr/dashboard/frontend/e2e/cluster/osds.e2e-spec.ts deleted file mode 100644 index f8a0939bc6c2..000000000000 --- a/src/pybind/mgr/dashboard/frontend/e2e/cluster/osds.e2e-spec.ts +++ /dev/null @@ -1,71 +0,0 @@ -import { $$, by, element } from 'protractor'; -import { OSDsPageHelper } from './osds.po'; - -describe('OSDs page', () => { - let osds: OSDsPageHelper; - - beforeAll(async () => { - osds = new OSDsPageHelper(); - await osds.navigateTo(); - }); - - afterEach(async () => { - await OSDsPageHelper.checkConsole(); - }); - - describe('breadcrumb and tab tests', () => { - it('should open and show breadcrumb', async () => { - await osds.waitTextToBePresent(osds.getBreadcrumb(), 'OSDs'); - }); - - it('should show two tabs', async () => { - await expect(osds.getTabsCount()).toEqual(2); - }); - - it('should show OSDs list tab at first', async () => { - await expect(osds.getTabText(0)).toEqual('OSDs List'); - }); - - it('should show overall performance as a second tab', async () => { - await expect(osds.getTabText(1)).toEqual('Overall Performance'); - }); - }); - - describe('check existence of fields on OSD page', () => { - it('should check that number of rows and count in footer match', async () => { - await expect(osds.getTableTotalCount()).toEqual(osds.getTableRows().count()); - }); - - it('should verify that buttons exist', async () => { - await expect(element(by.cssContainingText('button', 'Create')).isPresent()).toBe(true); - await expect( - element(by.cssContainingText('button', 'Cluster-wide configuration')).isPresent() - ).toBe(true); - }); - - describe('by selecting one row in OSDs List', () => { - beforeAll(async () => { - await osds.waitClickableAndClick(osds.getFirstExpandCollapseElement()); - }); - - it('should verify that selected footer increases', async () => { - await expect(osds.getTableSelectedCount()).toEqual(1); - }); - - it('should show the correct text for the tab labels', async () => { - const tabHeadings = $$('#tabset-osd-details > div > tab').map((e) => - e.getAttribute('heading') - ); - await expect(tabHeadings).toEqual([ - 'Devices', - 'Attributes (OSD map)', - 'Metadata', - 'Device health', - 'Performance counter', - 'Histogram', - 'Performance Details' - ]); - }); - }); - }); -}); diff --git a/src/pybind/mgr/dashboard/frontend/e2e/cluster/osds.po.ts b/src/pybind/mgr/dashboard/frontend/e2e/cluster/osds.po.ts deleted file mode 100644 index a96898920727..000000000000 --- a/src/pybind/mgr/dashboard/frontend/e2e/cluster/osds.po.ts +++ /dev/null @@ -1,5 +0,0 @@ -import { PageHelper } from '../page-helper.po'; - -export class OSDsPageHelper extends PageHelper { - pages = { index: '/#/osd' }; -} diff --git a/src/pybind/mgr/dashboard/frontend/e2e/filesystems/filesystems.e2e-spec.ts b/src/pybind/mgr/dashboard/frontend/e2e/filesystems/filesystems.e2e-spec.ts deleted file mode 100644 index 2d860c438106..000000000000 --- a/src/pybind/mgr/dashboard/frontend/e2e/filesystems/filesystems.e2e-spec.ts +++ /dev/null @@ -1,23 +0,0 @@ -import { FilesystemsPageHelper } from './filesystems.po'; - -describe('Filesystems page', () => { - let filesystems: FilesystemsPageHelper; - - beforeAll(() => { - filesystems = new FilesystemsPageHelper(); - }); - - afterEach(async () => { - await FilesystemsPageHelper.checkConsole(); - }); - - describe('breadcrumb test', () => { - beforeAll(async () => { - await filesystems.navigateTo(); - }); - - it('should open and show breadcrumb', async () => { - await filesystems.waitTextToBePresent(filesystems.getBreadcrumb(), 'Filesystems'); - }); - }); -}); diff --git a/src/pybind/mgr/dashboard/frontend/e2e/filesystems/filesystems.po.ts b/src/pybind/mgr/dashboard/frontend/e2e/filesystems/filesystems.po.ts deleted file mode 100644 index eedbd855c634..000000000000 --- a/src/pybind/mgr/dashboard/frontend/e2e/filesystems/filesystems.po.ts +++ /dev/null @@ -1,5 +0,0 @@ -import { PageHelper } from '../page-helper.po'; - -export class FilesystemsPageHelper extends PageHelper { - pages = { index: '/#/cephfs' }; -} diff --git a/src/pybind/mgr/dashboard/frontend/e2e/nfs/nfs.e2e-spec.ts b/src/pybind/mgr/dashboard/frontend/e2e/nfs/nfs.e2e-spec.ts deleted file mode 100644 index dc697451d24a..000000000000 --- a/src/pybind/mgr/dashboard/frontend/e2e/nfs/nfs.e2e-spec.ts +++ /dev/null @@ -1,23 +0,0 @@ -import { NfsPageHelper } from './nfs.po'; - -describe('Nfs page', () => { - let nfs: NfsPageHelper; - - beforeAll(() => { - nfs = new NfsPageHelper(); - }); - - afterEach(async () => { - await NfsPageHelper.checkConsole(); - }); - - describe('breadcrumb test', () => { - beforeAll(async () => { - await nfs.navigateTo(); - }); - - it('should open and show breadcrumb', async () => { - await nfs.waitTextToBePresent(nfs.getBreadcrumb(), 'NFS'); - }); - }); -}); diff --git a/src/pybind/mgr/dashboard/frontend/e2e/nfs/nfs.po.ts b/src/pybind/mgr/dashboard/frontend/e2e/nfs/nfs.po.ts deleted file mode 100644 index d3db8ecca603..000000000000 --- a/src/pybind/mgr/dashboard/frontend/e2e/nfs/nfs.po.ts +++ /dev/null @@ -1,5 +0,0 @@ -import { PageHelper } from '../page-helper.po'; - -export class NfsPageHelper extends PageHelper { - pages = { index: '/#/nfs' }; -} diff --git a/src/pybind/mgr/dashboard/frontend/e2e/page-helper.po.ts b/src/pybind/mgr/dashboard/frontend/e2e/page-helper.po.ts deleted file mode 100644 index b5d4f5fb23e8..000000000000 --- a/src/pybind/mgr/dashboard/frontend/e2e/page-helper.po.ts +++ /dev/null @@ -1,348 +0,0 @@ -import { - $, - $$, - browser, - by, - element, - ElementArrayFinder, - ElementFinder, - protractor -} from 'protractor'; - -const EC = browser.ExpectedConditions; -const TIMEOUT = 20000; - -interface Pages { - index: string; -} - -export abstract class PageHelper { - pages: Pages; - - /** - * Checks if there are any errors on the browser - * - * @static - * @memberof Helper - */ - static async checkConsole() { - let browserLog = await browser.manage().logs().get('browser'); - - browserLog = browserLog.filter((log) => log.level.value > 900); - - if (browserLog.length > 0) { - console.log('\n log: ' + require('util').inspect(browserLog)); - } - - await expect(browserLog.length).toEqual(0); - } - - /** - * Decorator to be used on Helper methods to restrict access to one particular URL. This shall - * help developers to prevent and highlight mistakes. It also reduces boilerplate code and by - * thus, increases readability. - */ - static restrictTo(page: string): Function { - return (target: any, propertyKey: string, descriptor: PropertyDescriptor) => { - const fn: Function = descriptor.value; - descriptor.value = function (...args: any) { - return browser - .getCurrentUrl() - .then((url) => - url.endsWith(page) - ? fn.apply(this, args) - : Promise.reject( - `Method ${target.constructor.name}::${propertyKey} is supposed to be ` + - `run on path "${page}", but was run on URL "${url}"` - ) - ); - }; - }; - } - - /** - * Get the active breadcrumb item. - */ - getBreadcrumb(): ElementFinder { - return $('.breadcrumb-item.active'); - } - - async getTabText(index: number): Promise { - return $$('.nav.nav-tabs li').get(index).getText(); - } - - async getTableTotalCount(): Promise { - const text = await $$('.datatable-footer-inner .page-count span') - .filter(async (e) => (await e.getText()).includes('total')) - .first() - .getText(); - return Number(text.match(/(\d+)\s+total/)[1]); - } - - async getTableSelectedCount(): Promise { - const text = await $$('.datatable-footer-inner .page-count span') - .filter(async (e) => (await e.getText()).includes('selected')) - .first() - .getText(); - return Number(text.match(/(\d+)\s+selected/)[1]); - } - - async getTableFoundCount(): Promise { - const text = await $$('.datatable-footer-inner .page-count span') - .filter(async (e) => (await e.getText()).includes('found')) - .first() - .getText(); - return Number(text.match(/(\d+)\s+found/)[1]); - } - - getFirstTableCellWithText(content: string): ElementFinder { - return element.all(by.cssContainingText('.datatable-body-cell-label', content)).first(); - } - - getFirstExpandCollapseElement(): ElementFinder { - return element.all(by.className('tc_expand-collapse')).first(); - } - - getExpandCollapseElement(content: string): ElementFinder { - const tableRow = element(by.cssContainingText('.datatable-body-row', content)); - return tableRow.element(by.className('tc_expand-collapse')); - } - - getTableRow(content: string) { - return element(by.cssContainingText('.datatable-body-row', content)); - } - - getTable(): ElementFinder { - return $('.datatable-body'); - } - - async getTabsCount(): Promise { - return $$('.nav.nav-tabs li').count(); - } - - /** - * Ceph Dashboards' tag is not visible. Instead of the real checkbox, a - * replacement is shown which is supposed to have an adapted style. The replacement checkbox shown - * is part of the label and is rendered in the "::before" pseudo element of the label, hence the - * label is always clicked when the user clicks the replacement checkbox. - * - * This method finds corresponding label to the given checkbox and clicks it instead of the (fake) - * checkbox, like it is the case with real users. - * - * Alternatively, the checkbox' label can be passed. - * - * @param elem The checkbox or corresponding label - */ - async clickCheckbox(elem: ElementFinder): Promise { - const tagName = await elem.getTagName(); - let label: ElementFinder = null; // Both types are clickable - - await this.waitPresence(elem); - if (tagName === 'input') { - if ((await elem.getAttribute('type')) === 'checkbox') { - label = elem.element(by.xpath('..')).$(`label[for="${await elem.getAttribute('id')}"]`); - } else { - return Promise.reject('element must be of type checkbox'); - } - } else if (tagName === 'label') { - label = elem; - } else { - return Promise.reject( - `element <${tagName}> is not of the correct type. You need to pass a checkbox or label` - ); - } - - return this.waitClickableAndClick(label); - } - - /** - * Helper method to select an option inside a select element. - * This method will also expect that the option was set. - * @param option The option text (not value) to be selected. - */ - async selectOption(selectionName: string, option: string) { - await element(by.cssContainingText(`select[name=${selectionName}] option`, option)).click(); - return this.expectSelectOption(selectionName, option); - } - - /** - * Helper method to expect a set option inside a select element. - * @param option The selected option text (not value) that is to - * be expected. - */ - async expectSelectOption(selectionName: string, option: string) { - return expect( - element(by.css(`select[name=${selectionName}] option:checked`)).getText() - ).toContain(option); - } - - /** - * Returns the cell with the content given in `content`. Will not return a rejected Promise if the - * table cell hasn't been found. It behaves this way to enable to wait for - * visibility/invisibility/presence of the returned element. - * - * It will return a rejected Promise if the result is ambiguous, though. That means if the search - * for content has been completed, but more than a single row is shown in the data table. - */ - async getTableCellByContent(content: string): Promise { - const searchInput = $('#pool-list > div .search input'); - const rowAmountInput = $('#pool-list > div > div > .dataTables_paginate input'); - const footer = $('#pool-list > div datatable-footer'); - - await rowAmountInput.clear(); - await rowAmountInput.sendKeys('10'); - await searchInput.clear(); - await searchInput.sendKeys(content); - - const count = Number(await footer.getAttribute('ng-reflect-row-count')); - if (count !== 0 && count > 1) { - return Promise.reject('getTableCellByContent: Result is ambiguous'); - } else { - return Promise.resolve( - element( - by.cssContainingText('.datatable-body-cell-label', new RegExp(`^\\s${content}\\s$`)) - ) - ); - } - } - - /** - * Used when .clear() does not work on a text box, sends a Ctrl + a, BACKSPACE - */ - async clearInput(elem: ElementFinder) { - const types = ['text', 'number']; - if ((await elem.getTagName()) === 'input' && types.includes(await elem.getAttribute('type'))) { - return await elem.sendKeys( - protractor.Key.chord(protractor.Key.CONTROL, 'a'), - protractor.Key.BACK_SPACE - ); - } else { - return Promise.reject(`Element ${elem} does not match the expected criteria.`); - } - } - - async navigateTo(page: string = null) { - page = page || 'index'; - const url = this.pages[page]; - await browser.get(url); - } - - async navigateBack() { - await browser.navigate().back(); - } - - getDataTables(): ElementArrayFinder { - return $$('cd-table'); - } - - /** - * Gets column headers of table - */ - getDataTableHeaders(): ElementArrayFinder { - return $$('.datatable-header'); - } - - /** - * Grabs striped tables - */ - getStatusTables(): ElementArrayFinder { - return $$('.table.table-striped'); - } - - /** - * Grabs legends above tables - */ - getLegends(): ElementArrayFinder { - return $$('legend'); - } - - getToast() { - return $('.ngx-toastr'); - } - - async waitPresence(elem: ElementFinder, message?: string) { - return browser.wait(EC.presenceOf(elem), TIMEOUT, message); - } - - async waitStaleness(elem: ElementFinder, message?: string) { - return browser.wait(EC.stalenessOf(elem), TIMEOUT, message); - } - - /** - * This method will wait for the element to be clickable and then click it. - */ - async waitClickableAndClick(elem: ElementFinder, message?: string) { - await browser.wait(EC.elementToBeClickable(elem), TIMEOUT, message); - return elem.click(); - } - - async waitVisibility(elem: ElementFinder, message?: string) { - return browser.wait(EC.visibilityOf(elem), TIMEOUT, message); - } - - async waitInvisibility(elem: ElementFinder, message?: string) { - return browser.wait(EC.invisibilityOf(elem), TIMEOUT, message); - } - - async waitTextToBePresent(elem: ElementFinder, text: string, message?: string) { - return browser.wait(EC.textToBePresentInElement(elem, text), TIMEOUT, message); - } - - async waitTextNotPresent(elem: ElementFinder, text: string, message?: string) { - return browser.wait(EC.not(EC.textToBePresentInElement(elem, text)), TIMEOUT, message); - } - - async waitFn(func: Function, message?: string, timeout: number = TIMEOUT) { - return browser.wait(func, timeout, message); - } - - getFirstCell(): ElementFinder { - return $$('.datatable-body-cell-label').first(); - } - - /** - * This is a generic method to delete table rows. - * It will select the first row that contains the provided name and delete it. - * After that it will wait until the row is no longer displayed. - */ - async delete(name: string): Promise { - // Selects row - await this.waitClickableAndClick(this.getFirstTableCellWithText(name)); - - // Clicks on table Delete button - await $$('.table-actions button.dropdown-toggle').first().click(); // open submenu - await $('li.delete a').click(); // click on "delete" menu item - - // Confirms deletion - await this.clickCheckbox($('.custom-control-label')); - await element(by.cssContainingText('button', 'Delete')).click(); - - // Waits for item to be removed from table - return this.waitStaleness(this.getFirstTableCellWithText(name)); - } - - getTableRows() { - return $$('datatable-row-wrapper'); - } - - /** - * Uncheck all checked table rows. - */ - async uncheckAllTableRows() { - await $$( - '.datatable-body-cell-label .datatable-checkbox input[type=checkbox]:checked' - ).each((e: ElementFinder) => e.click()); - } - - async filterTable(name: string, option: string) { - await this.waitClickableAndClick($('.tc_filter_name > a')); - await element(by.cssContainingText(`.tc_filter_name .dropdown-item`, name)).click(); - - await this.waitClickableAndClick($('.tc_filter_option > a')); - await element(by.cssContainingText(`.tc_filter_option .dropdown-item`, option)).click(); - } - - async clearTableSearchInput() { - return this.waitClickableAndClick($('cd-table .search button')); - } -} diff --git a/src/pybind/mgr/dashboard/frontend/e2e/pools/pools.e2e-spec.ts b/src/pybind/mgr/dashboard/frontend/e2e/pools/pools.e2e-spec.ts deleted file mode 100644 index 7668dc2ccfa1..000000000000 --- a/src/pybind/mgr/dashboard/frontend/e2e/pools/pools.e2e-spec.ts +++ /dev/null @@ -1,52 +0,0 @@ -import { PoolPageHelper } from './pools.po'; - -describe('Pools page', () => { - let pools: PoolPageHelper; - const poolName = 'pool_e2e_pool/test'; - - beforeAll(async () => { - pools = new PoolPageHelper(); - await pools.navigateTo(); - }); - - afterEach(async () => { - await PoolPageHelper.checkConsole(); - }); - - describe('breadcrumb and tab tests', () => { - it('should open and show breadcrumb', async () => { - await pools.waitTextToBePresent(pools.getBreadcrumb(), 'Pools'); - }); - - it('should show two tabs', async () => { - await expect(pools.getTabsCount()).toEqual(2); - }); - - it('should show pools list tab at first', async () => { - await expect(pools.getTabText(0)).toEqual('Pools List'); - }); - - it('should show overall performance as a second tab', async () => { - await expect(pools.getTabText(1)).toEqual('Overall Performance'); - }); - }); - - it('should create a pool', async () => { - await pools.exist(poolName, false); - await pools.navigateTo('create'); - await pools.create(poolName, 8); - await pools.navigateTo(); - await pools.exist(poolName, true); - }); - - it('should edit a pools placement group', async () => { - await pools.exist(poolName, true); - await pools.navigateTo(); - await pools.edit_pool_pg(poolName, 32); - }); - - it('should delete a pool', async () => { - await pools.navigateTo(); - await pools.delete(poolName); - }); -}); diff --git a/src/pybind/mgr/dashboard/frontend/e2e/pools/pools.po.ts b/src/pybind/mgr/dashboard/frontend/e2e/pools/pools.po.ts deleted file mode 100644 index bda20d6f5f1d..000000000000 --- a/src/pybind/mgr/dashboard/frontend/e2e/pools/pools.po.ts +++ /dev/null @@ -1,86 +0,0 @@ -import { $, by, element, protractor } from 'protractor'; -import { PageHelper } from '../page-helper.po'; - -const pages = { - index: '/#/pool', - create: '/#/pool/create' -}; - -export class PoolPageHelper extends PageHelper { - pages = pages; - - private isPowerOf2(n: number): boolean { - // tslint:disable-next-line: no-bitwise - return (n & (n - 1)) === 0; - } - - @PageHelper.restrictTo(pages.index) - async exist(name: string, oughtToBePresent = true) { - const tableCell = await this.getTableCellByContent(name); - const waitFn = oughtToBePresent ? this.waitVisibility : this.waitInvisibility; - try { - await waitFn(tableCell); - } catch (e) { - const visibility = oughtToBePresent ? 'invisible' : 'visible'; - const msg = `Pool "${name}" is ${visibility}, but should not be. Waiting for a change timed out`; - return Promise.reject(msg); - } - return Promise.resolve(); - } - - @PageHelper.restrictTo(pages.create) - async create(name: string, placement_groups: number, ...apps: string[]): Promise { - const nameInput = $('input[name=name]'); - await nameInput.clear(); - if (!this.isPowerOf2(placement_groups)) { - return Promise.reject(`Placement groups ${placement_groups} are not a power of 2`); - } - await nameInput.sendKeys(name); - await this.selectOption('poolType', 'replicated'); - - await this.expectSelectOption('pgAutoscaleMode', 'on'); - await this.selectOption('pgAutoscaleMode', 'off'); // To show pgNum field - await $('input[name=pgNum]').sendKeys( - protractor.Key.CONTROL, - 'a', - protractor.Key.NULL, - placement_groups - ); - await this.setApplications(apps); - await element(by.css('cd-submit-button')).click(); - - return Promise.resolve(); - } - - async edit_pool_pg(name: string, new_pg: number, wait = true): Promise { - if (!this.isPowerOf2(new_pg)) { - return Promise.reject(`Placement groups ${new_pg} are not a power of 2`); - } - const elem = await this.getTableCellByContent(name); - await this.waitClickableAndClick(elem); // select pool from the table - await element(by.cssContainingText('button', 'Edit')).click(); // click edit button - await this.waitTextToBePresent(this.getBreadcrumb(), 'Edit'); // verify we are now on edit page - await $('input[name=pgNum]').sendKeys(protractor.Key.CONTROL, 'a', protractor.Key.NULL, new_pg); - await element(by.css('cd-submit-button')).click(); - const str = `${new_pg} active+clean`; - await this.waitVisibility(this.getTableRow(name), 'Timed out waiting for table row to load'); - if (wait) { - await this.waitTextToBePresent( - this.getTableRow(name), - str, - 'Timed out waiting for placement group to be updated' - ); - } - } - - private async setApplications(apps: string[]) { - if (!apps || apps.length === 0) { - return; - } - await element(by.css('.float-left.mr-2.select-menu-edit')).click(); - await this.waitVisibility(element(by.css('.popover-content.popover-body'))); - apps.forEach( - async (app) => await element(by.cssContainingText('.select-menu-item-content', app)).click() - ); - } -} diff --git a/src/pybind/mgr/dashboard/frontend/e2e/rgw/buckets.e2e-spec.ts b/src/pybind/mgr/dashboard/frontend/e2e/rgw/buckets.e2e-spec.ts deleted file mode 100644 index 8dbb80ead3e1..000000000000 --- a/src/pybind/mgr/dashboard/frontend/e2e/rgw/buckets.e2e-spec.ts +++ /dev/null @@ -1,70 +0,0 @@ -import { $ } from 'protractor'; -import { BucketsPageHelper } from './buckets.po'; - -describe('RGW buckets page', () => { - let buckets: BucketsPageHelper; - const bucket_name = '000test'; - - beforeAll(async () => { - buckets = new BucketsPageHelper(); - }); - - afterEach(async () => { - await BucketsPageHelper.checkConsole(); - }); - - describe('breadcrumb tests', () => { - beforeEach(async () => { - await buckets.navigateTo(); - }); - - it('should open and show breadcrumb', async () => { - await expect($('.breadcrumb-item.active').getText()).toBe('Buckets'); - }); - }); - - describe('create, edit & delete bucket tests', () => { - beforeEach(async () => { - await buckets.navigateTo(); - await buckets.uncheckAllTableRows(); - }); - - it('should create bucket', async () => { - await buckets.navigateTo('create'); - await buckets.create( - bucket_name, - '0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef', - 'default-placement' - ); - await expect(buckets.getFirstTableCellWithText(bucket_name).isPresent()).toBe(true); - }); - - it('should edit bucket', async () => { - await buckets.edit(bucket_name, 'dev'); - await expect(buckets.getTable().getText()).toMatch('dev'); - }); - - it('should delete bucket', async () => { - await buckets.delete(bucket_name); - }); - }); - - describe('Invalid Input in Create and Edit tests', () => { - it('should test invalid inputs in create fields', async () => { - await buckets.testInvalidCreate(); - }); - - it('should test invalid input in edit owner field', async () => { - await buckets.navigateTo('create'); - await buckets.create( - '000rq', - '0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef', - 'default-placement' - ); - await buckets.testInvalidEdit('000rq'); - await buckets.navigateTo(); - await buckets.uncheckAllTableRows(); - await buckets.delete('000rq'); - }); - }); -}); diff --git a/src/pybind/mgr/dashboard/frontend/e2e/rgw/buckets.po.ts b/src/pybind/mgr/dashboard/frontend/e2e/rgw/buckets.po.ts deleted file mode 100644 index ab14627c2a53..000000000000 --- a/src/pybind/mgr/dashboard/frontend/e2e/rgw/buckets.po.ts +++ /dev/null @@ -1,192 +0,0 @@ -import { by, element } from 'protractor'; -import { PageHelper } from '../page-helper.po'; - -const pages = { - index: '/#/rgw/bucket', - create: '/#/rgw/bucket/create' -}; - -export class BucketsPageHelper extends PageHelper { - pages = pages; - versioningStateEnabled = 'Enabled'; - versioningStateSuspended = 'Suspended'; - - private async selectOwner(owner: string) { - return this.selectOption('owner', owner); - } - - private async selectPlacementTarget(placementTarget: string) { - return this.selectOption('placement-target', placementTarget); - } - - /** - * TODO add check to verify the existance of the bucket! - * TODO let it print a meaningful error message (for devs) if it does not exist! - */ - @PageHelper.restrictTo(pages.create) - async create(name: string, owner: string, placementTarget: string) { - // Enter in bucket name - await element(by.id('bid')).sendKeys(name); - - // Select bucket owner - await this.selectOwner(owner); - await expect(element(by.id('owner')).getAttribute('class')).toContain('ng-valid'); - - // Select bucket placement target: - await this.selectPlacementTarget(placementTarget); - await expect(element(by.id('placement-target')).getAttribute('class')).toContain('ng-valid'); - - // Click the create button and wait for bucket to be made - const createButton = element(by.cssContainingText('button', 'Create Bucket')); - await createButton.click(); - - return this.waitPresence( - this.getFirstTableCellWithText(name), - 'Timed out waiting for bucket creation' - ); - } - - @PageHelper.restrictTo(pages.index) - async edit(name: string, new_owner: string) { - await this.waitClickableAndClick(this.getFirstTableCellWithText(name)); // wait for table to load and click - await element(by.cssContainingText('button', 'Edit')).click(); // click button to move to edit page - await this.waitTextToBePresent(this.getBreadcrumb(), 'Edit'); - await expect(element(by.css('input[name=placement-target]')).getAttribute('value')).toBe( - 'default-placement' - ); - await this.selectOwner(new_owner); - - // Enable versioning - await expect(element(by.css('input[id=versioning]')).getAttribute('checked')).toBeFalsy(); - await element(by.css('label[for=versioning]')).click(); - await expect(element(by.css('input[id=versioning]')).getAttribute('checked')).toBeTruthy(); - - await element(by.cssContainingText('button', 'Edit Bucket')).click(); - - // wait to be back on buckets page with table visible and click - await this.waitClickableAndClick( - this.getExpandCollapseElement(name), - 'Could not return to buckets page and load table after editing bucket' - ); - - // check its details table for edited owner field - let bucketDataTable = element.all(by.css('.table.table-striped.table-bordered')).first(); - await expect(bucketDataTable.getText()).toMatch(new_owner); - - // Check versioning enabled: - const ownerValueCell = bucketDataTable.all(by.css('tr')).get(2).all(by.css('td')).last(); - await expect(ownerValueCell.getText()).toEqual(new_owner); - let versioningValueCell = bucketDataTable.all(by.css('tr')).get(11).all(by.css('td')).last(); - await expect(versioningValueCell.getText()).toEqual(this.versioningStateEnabled); - - // Disable versioning: - await this.uncheckAllTableRows(); - await this.waitClickableAndClick(this.getFirstTableCellWithText(name)); // wait for table to load and click - await element(by.cssContainingText('button', 'Edit')).click(); // click button to move to edit page - await this.waitTextToBePresent(this.getBreadcrumb(), 'Edit'); - await element(by.css('label[for=versioning]')).click(); - await expect(element(by.css('input[id=versioning]')).getAttribute('checked')).toBeFalsy(); - await element(by.cssContainingText('button', 'Edit Bucket')).click(); - - // Check versioning suspended: - await this.waitClickableAndClick( - this.getExpandCollapseElement(name), - 'Could not return to buckets page and load table after editing bucket' - ); - bucketDataTable = element.all(by.css('.table.table-striped.table-bordered')).first(); - versioningValueCell = bucketDataTable.all(by.css('tr')).get(11).all(by.css('td')).last(); - return expect(versioningValueCell.getText()).toEqual(this.versioningStateSuspended); - } - - async testInvalidCreate() { - await this.navigateTo('create'); - const nameInputField = element(by.id('bid')); // Grabs name box field - - // Gives an invalid name (too short), then waits for dashboard to determine validity - await nameInputField.sendKeys('rq'); - - await element(by.id('owner')).click(); // To trigger a validation - - await this.waitFn(async () => { - // Waiting for website to decide if name is valid or not - const klass = await nameInputField.getAttribute('class'); - return !klass.includes('ng-pending'); - }, 'Timed out waiting for dashboard to decide bucket name validity'); - - // Check that name input field was marked invalid in the css - await expect(nameInputField.getAttribute('class')).toContain('ng-invalid'); - - // Check that error message was printed under name input field - await expect(element(by.css('#bid + .invalid-feedback')).getText()).toMatch( - 'The value is not valid.' - ); - - // Test invalid owner input - // select some valid option. The owner drop down error message will not appear unless a valid user was selected at - // one point before the invalid placeholder user is selected. - await this.selectOwner('dev'); - - // select the first option, which is invalid because it is a placeholder - await this.selectOwner('Select a user'); - - await nameInputField.click(); - - // Check that owner drop down field was marked invalid in the css - await expect(element(by.id('owner')).getAttribute('class')).toContain('ng-invalid'); - - // Check that error message was printed under owner drop down field - await expect(element(by.css('#owner + .invalid-feedback')).getText()).toMatch( - 'This field is required.' - ); - - // Check invalid placement target input - await this.selectOwner('dev'); - // The drop down error message will not appear unless a valid option is previsously selected. - await this.selectPlacementTarget('default-placement'); - await this.selectPlacementTarget('Select a placement target'); - await nameInputField.click(); // Trigger validation - await expect(element(by.id('placement-target')).getAttribute('class')).toContain('ng-invalid'); - await expect(element(by.css('#placement-target + .invalid-feedback')).getText()).toMatch( - 'This field is required.' - ); - - // Clicks the Create Bucket button but the page doesn't move. Done by testing - // for the breadcrumb - await element(by.cssContainingText('button', 'Create Bucket')).click(); // Clicks Create Bucket button - await this.waitTextToBePresent(this.getBreadcrumb(), 'Create'); - // content in fields seems to subsist through tests if not cleared, so it is cleared - await nameInputField.clear(); - return element(by.cssContainingText('button', 'Cancel')).click(); - } - - async testInvalidEdit(name: string) { - await this.navigateTo(); - - await this.waitClickableAndClick(this.getFirstTableCellWithText(name)); // wait for table to load and click - await element(by.cssContainingText('button', 'Edit')).click(); // click button to move to edit page - - await this.waitTextToBePresent(this.getBreadcrumb(), 'Edit'); - - await expect(element(by.css('input[id=versioning]')).getAttribute('checked')).toBeFalsy(); - - // Chooses 'Select a user' rather than a valid owner on Edit Bucket page - // and checks if it's an invalid input - - // select the first option, which is invalid because it is a placeholder - await this.selectOwner('Select a user'); - - // Changes when updated to bootstrap 4 -> Error message takes a long time to appear unless another field - // is clicked on. For that reason, I'm having the test click on the edit button before checking for errors - await element(by.cssContainingText('button', 'Edit Bucket')).click(); - - // Check that owner drop down field was marked invalid in the css - await expect(element(by.id('owner')).getAttribute('class')).toContain('ng-invalid'); - - // Check that error message was printed under owner drop down field - await expect(element(by.css('#owner + .invalid-feedback')).getText()).toMatch( - 'This field is required.' - ); - - await this.waitTextToBePresent(this.getBreadcrumb(), 'Edit'); - } -} diff --git a/src/pybind/mgr/dashboard/frontend/e2e/rgw/daemons.e2e-spec.ts b/src/pybind/mgr/dashboard/frontend/e2e/rgw/daemons.e2e-spec.ts deleted file mode 100644 index 0a9e551db328..000000000000 --- a/src/pybind/mgr/dashboard/frontend/e2e/rgw/daemons.e2e-spec.ts +++ /dev/null @@ -1,45 +0,0 @@ -import { DaemonsPageHelper } from './daemons.po'; - -describe('RGW daemons page', () => { - let daemons: DaemonsPageHelper; - - beforeAll(() => { - daemons = new DaemonsPageHelper(); - }); - - afterEach(async () => { - await DaemonsPageHelper.checkConsole(); - }); - - describe('breadcrumb and tab tests', () => { - beforeAll(async () => { - await daemons.navigateTo(); - }); - - it('should open and show breadcrumb', async () => { - await daemons.waitTextToBePresent(daemons.getBreadcrumb(), 'Daemons'); - }); - - it('should show two tabs', async () => { - await expect(daemons.getTabsCount()).toEqual(2); - }); - - it('should show daemons list tab at first', async () => { - await expect(daemons.getTabText(0)).toEqual('Daemons List'); - }); - - it('should show overall performance as a second tab', async () => { - await expect(daemons.getTabText(1)).toEqual('Overall Performance'); - }); - }); - - describe('details and performance counters table tests', () => { - beforeAll(async () => { - await daemons.navigateTo(); - }); - - it('should check that details/performance tables are visible when daemon is selected', async () => { - await daemons.checkTables(); - }); - }); -}); diff --git a/src/pybind/mgr/dashboard/frontend/e2e/rgw/daemons.po.ts b/src/pybind/mgr/dashboard/frontend/e2e/rgw/daemons.po.ts deleted file mode 100644 index cd063803cb03..000000000000 --- a/src/pybind/mgr/dashboard/frontend/e2e/rgw/daemons.po.ts +++ /dev/null @@ -1,39 +0,0 @@ -import { $$, by, element } from 'protractor'; -import { PageHelper } from '../page-helper.po'; - -export class DaemonsPageHelper extends PageHelper { - pages = { index: '/#/rgw/daemon' }; - - async checkTables() { - await this.navigateTo(); - - // click on a daemon so details table appears - await $$('.datatable-body-cell-label').first().click(); - - const tab_container = $$('.tab-container').last(); - const details_table = tab_container.all(by.css('cd-table')).get(0); - const performance_counters_table = tab_container.all(by.css('cd-table')).get(1); - - // check details table is visible - await expect(details_table.isDisplayed()).toBe(true); - // check at least one field is present - await expect(details_table.getText()).toMatch('ceph_version'); - // check performance counters table is not currently visible - await expect(performance_counters_table.isDisplayed()).toBe(false); - - // click on performance counters tab and check table is loaded - await element(by.cssContainingText('.nav-link', 'Performance Counters')).click(); - await expect(performance_counters_table.isDisplayed()).toBe(true); - // check at least one field is present - await expect(performance_counters_table.getText()).toMatch('objecter.op_r'); - // check details table is not currently visible - await expect(details_table.isDisplayed()).toBe(false); - - // click on performance details tab - await element(by.cssContainingText('.nav-link', 'Performance Details')).click(); - // checks the other tabs' content isn't visible - await expect(details_table.isDisplayed()).toBe(false); - await expect(performance_counters_table.isDisplayed()).toBe(false); - // TODO: Expect Grafana iFrame - } -} diff --git a/src/pybind/mgr/dashboard/frontend/e2e/rgw/users.e2e-spec.ts b/src/pybind/mgr/dashboard/frontend/e2e/rgw/users.e2e-spec.ts deleted file mode 100644 index 2e93046e0dc6..000000000000 --- a/src/pybind/mgr/dashboard/frontend/e2e/rgw/users.e2e-spec.ts +++ /dev/null @@ -1,55 +0,0 @@ -import { UsersPageHelper } from './users.po'; - -describe('RGW users page', () => { - let users: UsersPageHelper; - const user_name = '000user_create_edit_delete'; - - beforeAll(() => { - users = new UsersPageHelper(); - }); - - afterEach(async () => { - await UsersPageHelper.checkConsole(); - }); - - describe('breadcrumb tests', () => { - beforeEach(async () => { - await users.navigateTo(); - }); - - it('should open and show breadcrumb', async () => { - await users.waitTextToBePresent(users.getBreadcrumb(), 'Users'); - }); - }); - - describe('create, edit & delete user tests', () => { - beforeEach(async () => { - await users.navigateTo(); - await users.uncheckAllTableRows(); - }); - - it('should create user', async () => { - await users.navigateTo('create'); - await users.create(user_name, 'Some Name', 'original@website.com', '1200'); - await expect(users.getFirstTableCellWithText(user_name).isPresent()).toBe(true); - }); - - it('should edit users full name, email and max buckets', async () => { - await users.edit(user_name, 'Another Identity', 'changed@othersite.com', '1969'); - }); - - it('should delete user', async () => { - await users.delete(user_name); - }); - }); - - describe('Invalid input tests', () => { - it('should put invalid input into user creation form and check fields are marked invalid', async () => { - await users.invalidCreate(); - }); - - it('should put invalid input into user edit form and check fields are marked invalid', async () => { - await users.invalidEdit(); - }); - }); -}); diff --git a/src/pybind/mgr/dashboard/frontend/e2e/rgw/users.po.ts b/src/pybind/mgr/dashboard/frontend/e2e/rgw/users.po.ts deleted file mode 100644 index 77aa572bdf74..000000000000 --- a/src/pybind/mgr/dashboard/frontend/e2e/rgw/users.po.ts +++ /dev/null @@ -1,191 +0,0 @@ -import { $, by, element } from 'protractor'; -import { protractor } from 'protractor/built/ptor'; -import { PageHelper } from '../page-helper.po'; - -const pages = { - index: '/#/rgw/user', - create: '/#/rgw/user/create' -}; - -export class UsersPageHelper extends PageHelper { - pages = pages; - - @PageHelper.restrictTo(pages.create) - async create(username: string, fullname: string, email: string, maxbuckets: string) { - // Enter in username - await element(by.id('uid')).sendKeys(username); - - // Enter in full name - await element(by.id('display_name')).click(); - await element(by.id('display_name')).sendKeys(fullname); - - // Enter in email - await element(by.id('email')).click(); - await element(by.id('email')).sendKeys(email); - - // Enter max buckets - await this.selectOption('max_buckets_mode', 'Custom'); - await element(by.id('max_buckets')).click(); - await element(by.id('max_buckets')).clear(); - await element(by.id('max_buckets')).sendKeys(maxbuckets); - - // Click the create button and wait for user to be made - await element(by.cssContainingText('button', 'Create User')).click(); - await this.waitPresence(this.getFirstTableCellWithText(username)); - } - - @PageHelper.restrictTo(pages.index) - async edit(name: string, new_fullname: string, new_email: string, new_maxbuckets: string) { - await this.waitClickableAndClick(this.getFirstTableCellWithText(name)); // wait for table to load and click - await element(by.cssContainingText('button', 'Edit')).click(); // click button to move to edit page - - await this.waitTextToBePresent(this.getBreadcrumb(), 'Edit'); - - // Change the full name field - await element(by.id('display_name')).click(); - await element(by.id('display_name')).clear(); - await element(by.id('display_name')).sendKeys(new_fullname); - - // Change the email field - await element(by.id('email')).click(); - await element(by.id('email')).clear(); - await element(by.id('email')).sendKeys(new_email); - - // Change the max buckets field - await this.selectOption('max_buckets_mode', 'Custom'); - await element(by.id('max_buckets')).click(); - await element(by.id('max_buckets')).clear(); - await element(by.id('max_buckets')).sendKeys(new_maxbuckets); - - const editbutton = element(by.cssContainingText('button', 'Edit User')); - await editbutton.click(); - // Click the user and check its details table for updated content - await this.waitClickableAndClick(this.getExpandCollapseElement(name)); - await expect($('.active.tab-pane').getText()).toMatch(new_fullname); // check full name was changed - await expect($('.active.tab-pane').getText()).toMatch(new_email); // check email was changed - await expect($('.active.tab-pane').getText()).toMatch(new_maxbuckets); // check max buckets was changed - } - - async invalidCreate() { - const uname = '000invalid_create_user'; - // creating this user in order to check that you can't give two users the same name - await this.navigateTo('create'); - await this.create(uname, 'xxx', 'xxx@xxx', '1'); - - await this.navigateTo('create'); - - const username_field = element(by.id('uid')); - - // No username had been entered. Field should be invalid - await expect(username_field.getAttribute('class')).toContain('ng-invalid'); - - // Try to give user already taken name. Should make field invalid. - await username_field.clear(); - await username_field.sendKeys(uname); - await expect(username_field.getAttribute('class')).toContain('ng-invalid'); - await element(by.id('display_name')).click(); // trigger validation check - await expect(element(by.css('#uid + .invalid-feedback')).getText()).toMatch( - 'The chosen user ID is already in use.' - ); - - // check that username field is marked invalid if username has been cleared off - await username_field.click(); - for (let i = 0; i < uname.length; i++) { - await username_field.sendKeys(protractor.Key.BACK_SPACE); - } - await expect(username_field.getAttribute('class')).toContain('ng-invalid'); - await element(by.id('display_name')).click(); // trigger validation check - await expect(element(by.css('#uid + .invalid-feedback')).getText()).toMatch( - 'This field is required.' - ); - - // No display name has been given so field should be invalid - await expect(element(by.id('display_name')).getAttribute('class')).toContain('ng-invalid'); - - // display name field should also be marked invalid if given input then emptied - await element(by.id('display_name')).click(); - await element(by.id('display_name')).sendKeys('a'); - await element(by.id('display_name')).sendKeys(protractor.Key.BACK_SPACE); - await expect(element(by.id('display_name')).getAttribute('class')).toContain('ng-invalid'); - await username_field.click(); // trigger validation check - await expect(element(by.css('#display_name + .invalid-feedback')).getText()).toMatch( - 'This field is required.' - ); - - // put invalid email to make field invalid - await element(by.id('email')).click(); - await element(by.id('email')).sendKeys('a'); - await expect(element(by.id('email')).getAttribute('class')).toContain('ng-invalid'); - await username_field.click(); // trigger validation check - await expect(element(by.css('#email + .invalid-feedback')).getText()).toMatch( - 'This is not a valid email address.' - ); - - // put negative max buckets to make field invalid - await this.expectSelectOption('max_buckets_mode', 'Custom'); - await element(by.id('max_buckets')).click(); - await element(by.id('max_buckets')).clear(); - await element(by.id('max_buckets')).sendKeys('-5'); - await expect(element(by.id('max_buckets')).getAttribute('class')).toContain('ng-invalid'); - await username_field.click(); // trigger validation check - await expect(element(by.css('#max_buckets + .invalid-feedback')).getText()).toMatch( - 'The entered value must be >= 1.' - ); - - await this.navigateTo(); - await this.delete(uname); - } - - async invalidEdit() { - const uname = '000invalid_edit_user'; - // creating this user to edit for the test - await this.navigateTo('create'); - await this.create(uname, 'xxx', 'xxx@xxx', '1'); - - await this.navigateTo(); - - // wait for table to load and click on the bucket you want to edit in the table - await this.waitClickableAndClick(this.getFirstTableCellWithText(uname)); - await element(by.cssContainingText('button', 'Edit')).click(); // click button to move to edit page - - await this.waitTextToBePresent(this.getBreadcrumb(), 'Edit'); - - // put invalid email to make field invalid - await element(by.id('email')).click(); - await element(by.id('email')).clear(); - await element(by.id('email')).sendKeys('a'); - await this.waitFn( - async () => !(await element(by.id('email')).getAttribute('class')).includes('ng-pending') - ); - await expect(element(by.id('email')).getAttribute('class')).toContain('ng-invalid'); - await element(by.id('display_name')).click(); // trigger validation check - await expect(element(by.css('#email + .invalid-feedback')).getText()).toMatch( - 'This is not a valid email address.' - ); - - // empty the display name field making it invalid - await element(by.id('display_name')).click(); - for (let i = 0; i < 3; i++) { - await element(by.id('display_name')).sendKeys(protractor.Key.BACK_SPACE); - } - await expect(element(by.id('display_name')).getAttribute('class')).toContain('ng-invalid'); - await element(by.id('email')).click(); // trigger validation check - await expect(element(by.css('#display_name + .invalid-feedback')).getText()).toMatch( - 'This field is required.' - ); - - // put negative max buckets to make field invalid - await this.expectSelectOption('max_buckets_mode', 'Custom'); - await element(by.id('max_buckets')).click(); - await element(by.id('max_buckets')).clear(); - await element(by.id('max_buckets')).sendKeys('-5'); - await expect(element(by.id('max_buckets')).getAttribute('class')).toContain('ng-invalid'); - await element(by.id('email')).click(); // trigger validation check - await expect(element(by.css('#max_buckets + .invalid-feedback')).getText()).toMatch( - 'The entered value must be >= 1.' - ); - - await this.navigateTo(); - await this.delete(uname); - } -} diff --git a/src/pybind/mgr/dashboard/frontend/e2e/tsconfig.e2e.json b/src/pybind/mgr/dashboard/frontend/e2e/tsconfig.e2e.json deleted file mode 100644 index e3d6ae7644a6..000000000000 --- a/src/pybind/mgr/dashboard/frontend/e2e/tsconfig.e2e.json +++ /dev/null @@ -1,15 +0,0 @@ -{ - "extends": "../tsconfig.json", - "compilerOptions": { - "outDir": "../out-tsc/e2e", - "baseUrl": "./", - "module": "commonjs", - "target": "es5", - "types": [ - "jasmine", - "jasminewd2", - "node" - ], - "noEmit": true - } -} diff --git a/src/pybind/mgr/dashboard/frontend/e2e/ui/dashboard.e2e-spec.ts b/src/pybind/mgr/dashboard/frontend/e2e/ui/dashboard.e2e-spec.ts deleted file mode 100644 index d15c358d5250..000000000000 --- a/src/pybind/mgr/dashboard/frontend/e2e/ui/dashboard.e2e-spec.ts +++ /dev/null @@ -1,140 +0,0 @@ -import { browser } from 'protractor'; -import { IscsiPageHelper } from '../block/iscsi.po'; -import { HostsPageHelper } from '../cluster/hosts.po'; -import { MonitorsPageHelper } from '../cluster/monitors.po'; -import { OSDsPageHelper } from '../cluster/osds.po'; -import { PageHelper } from '../page-helper.po'; -import { PoolPageHelper } from '../pools/pools.po'; -import { DaemonsPageHelper } from '../rgw/daemons.po'; -import { DashboardPageHelper } from './dashboard.po'; - -describe('Dashboard Main Page', () => { - let dashboard: DashboardPageHelper; - let daemons: DaemonsPageHelper; - let hosts: HostsPageHelper; - let osds: OSDsPageHelper; - let pools: PoolPageHelper; - let monitors: MonitorsPageHelper; - let iscsi: IscsiPageHelper; - - beforeAll(() => { - dashboard = new DashboardPageHelper(); - daemons = new DaemonsPageHelper(); - hosts = new HostsPageHelper(); - osds = new OSDsPageHelper(); - pools = new PoolPageHelper(); - monitors = new MonitorsPageHelper(); - iscsi = new IscsiPageHelper(); - }); - - afterEach(async () => { - await DashboardPageHelper.checkConsole(); - }); - - describe('Check that all hyperlinks on info cards lead to the correct page and fields exist', () => { - beforeEach(async () => { - await dashboard.navigateTo(); - }); - - it('should ensure that all linked info cards lead to correct page', async () => { - const expectationMap = { - Monitors: 'Monitors', - OSDs: 'OSDs', - Hosts: 'Hosts', - 'Object Gateways': 'Daemons', - 'iSCSI Gateways': 'Overview', - Pools: 'Pools' - }; - - for (const [linkText, breadcrumbText] of Object.entries(expectationMap)) { - await expect(browser.getCurrentUrl()).toContain('/#/dashboard'); - await dashboard.clickInfoCardLink(linkText); - await dashboard.waitTextToBePresent(dashboard.getBreadcrumb(), breadcrumbText); - await dashboard.navigateBack(); - } - }); - - it('should verify that info cards exist on dashboard in proper order', async () => { - // Ensures that info cards are all displayed on the dashboard tab while being in the proper - // order, checks for card title and position via indexing into a list of all info cards. - const order = [ - 'Cluster Status', - 'Monitors', - 'OSDs', - 'Manager Daemons', - 'Hosts', - 'Object Gateways', - 'Metadata Servers', - 'iSCSI Gateways', - 'Client IOPS', - 'Client Throughput', - 'Client Read/Write', - 'Recovery Throughput', - 'Scrub', - 'Pools', - 'Raw Capacity', - 'Objects', - 'PGs per OSD', - 'PG Status' - ]; - - for (let i = 0; i < order.length; i++) { - await expect((await dashboard.infoCard(i)).getText()).toContain( - order[i], - `Order of ${order[i]} seems to be wrong` - ); - } - }); - - it('should verify that info card group titles are present and in the right order', async () => { - await expect(browser.getCurrentUrl()).toContain('/#/dashboard'); - await expect(dashboard.infoGroupTitle(0)).toBe('Status'); - await expect(dashboard.infoGroupTitle(1)).toBe('Performance'); - await expect(dashboard.infoGroupTitle(2)).toBe('Capacity'); - }); - }); - - it('Should check that dashboard cards have correct information', async () => { - interface TestSpec { - cardName: string; - regexMatcher?: RegExp; - pageObject: PageHelper; - } - - const testSpecs: TestSpec[] = [ - { cardName: 'Object Gateways', regexMatcher: /(\d+)\s+total/, pageObject: daemons }, - { cardName: 'Monitors', regexMatcher: /(\d+)\s+\(quorum/, pageObject: monitors }, - { cardName: 'Hosts', regexMatcher: /(\d+)\s+total/, pageObject: hosts }, - { cardName: 'OSDs', regexMatcher: /(\d+)\s+total/, pageObject: osds }, - { cardName: 'Pools', pageObject: pools }, - { cardName: 'iSCSI Gateways', regexMatcher: /(\d+)\s+total/, pageObject: iscsi } - ]; - - for (let i = 0; i < testSpecs.length; i++) { - const spec = testSpecs[i]; - await dashboard.navigateTo(); - const infoCardBodyText = await dashboard.infoCardBodyText(spec.cardName); - let dashCount = 0; - if (spec.regexMatcher) { - const match = infoCardBodyText.match(new RegExp(spec.regexMatcher)); - if (match && match.length > 1) { - dashCount = Number(match[1]); - } else { - return Promise.reject( - `Regex ${spec.regexMatcher} did not find a match for card with name ` + - `${spec.cardName}` - ); - } - } else { - dashCount = Number(infoCardBodyText); - } - await spec.pageObject.navigateTo(); - const tableCount = await spec.pageObject.getTableTotalCount(); - await expect(dashCount).toBe( - tableCount, - `Text of card "${spec.cardName}" and regex "${spec.regexMatcher}" resulted in ${dashCount} ` + - `but did not match table count ${tableCount}` - ); - } - }); -}); diff --git a/src/pybind/mgr/dashboard/frontend/e2e/ui/dashboard.po.ts b/src/pybind/mgr/dashboard/frontend/e2e/ui/dashboard.po.ts deleted file mode 100644 index 1293d535c7d4..000000000000 --- a/src/pybind/mgr/dashboard/frontend/e2e/ui/dashboard.po.ts +++ /dev/null @@ -1,49 +0,0 @@ -import { $, $$, by, ElementFinder } from 'protractor'; -import { PageHelper } from '../page-helper.po'; - -export class DashboardPageHelper extends PageHelper { - pages = { - index: '/#/dashboard' - }; - - async infoGroupTitle(index: number): Promise { - return $$('.info-group-title').get(index).getText(); - } - - async clickInfoCardLink(cardName: string): Promise { - await $(`cd-info-card[cardtitle="${cardName}"]`).element(by.linkText(cardName)).click(); - } - - async infoCard(indexOrTitle: number | string): Promise { - let infoCards = $$('cd-info-card'); - if (typeof indexOrTitle === 'number') { - if ((await infoCards.count()) <= indexOrTitle) { - return Promise.reject( - `No element found for index ${indexOrTitle}. Elements array has ` + - `only ${await infoCards.count()} elements.` - ); - } - return infoCards.get(indexOrTitle); - } else if (typeof indexOrTitle === 'string') { - infoCards = infoCards.filter( - async (e) => (await e.$('.card-title').getText()) === indexOrTitle - ); - if ((await infoCards.count()) === 0) { - return Promise.reject(`No element found for title "${indexOrTitle}"`); - } - return infoCards.first(); - } - } - - async infoCardBodyText( - infoCard: ElementFinder | Promise | string - ): Promise { - let _infoCard: ElementFinder; - if (typeof infoCard === 'string') { - _infoCard = await this.infoCard(infoCard); - } else { - _infoCard = typeof infoCard.then === 'function' ? await infoCard : infoCard; - } - return _infoCard.$('.card-text').getText(); - } -} diff --git a/src/pybind/mgr/dashboard/frontend/e2e/ui/notification.e2e-spec.ts b/src/pybind/mgr/dashboard/frontend/e2e/ui/notification.e2e-spec.ts deleted file mode 100644 index 3aedae54829d..000000000000 --- a/src/pybind/mgr/dashboard/frontend/e2e/ui/notification.e2e-spec.ts +++ /dev/null @@ -1,73 +0,0 @@ -import { PoolPageHelper } from '../pools/pools.po'; -import { NotificationSidebarPageHelper } from './notification.po'; - -describe('Notification page', () => { - let notification: NotificationSidebarPageHelper; - let pools: PoolPageHelper; - - beforeAll(() => { - notification = new NotificationSidebarPageHelper(); - pools = new PoolPageHelper(); - }); - - afterEach(async () => { - await NotificationSidebarPageHelper.checkConsole(); - }); - - it('should open notification sidebar', async () => { - await notification.waitInvisibility(notification.getSidebar()); - await notification.open(); - await notification.waitVisibility(notification.getSidebar()); - }); - - it('should display a running task', async () => { - const poolName = 'e2e_notification_pool'; - - await pools.navigateTo('create'); - await pools.create(poolName, 8); - await pools.edit_pool_pg(poolName, 4, false); - await notification.waitStaleness(notification.getToast()); - - // Check that running task is shown. - await notification.open(); - await notification.waitFn(async () => { - const task = await notification.getTasks().first(); - const text = await task.getText(); - return text.includes(poolName); - }, 'Timed out verifying task.'); - - // Delete pool after task is complete (otherwise we get an error). - await notification.waitFn( - async () => { - const tasks = await notification.getTasks(); - return tasks.length === 0 ? true : !(await tasks[0].getText()).includes(poolName); - }, - 'Timed out waiting for task to complete.', - 40000 - ); - await pools.delete(poolName); - }); - - it('should have notifications', async () => { - await notification.open(); - await expect((await notification.getNotifications()).length).toBeGreaterThan(0); - }); - - it('should clear notifications', async () => { - await notification.waitStaleness(notification.getToast()); - await expect((await notification.getNotifications()).length).toBeGreaterThan(0); - await notification.waitVisibility(notification.getClearNotficationsBtn()); - - // It can happen that although notifications are cleared, by the time we check the - // notifications amount, another notification can appear, so we check it more than once (if needed). - await notification.waitClickableAndClick(notification.getClearNotficationsBtn()); - await notification.waitFn(async () => { - const notifications = await notification.getNotifications(); - if (notifications.length > 0) { - await notification.waitClickableAndClick(notification.getClearNotficationsBtn()); - return false; - } - return true; - }, 'Timed out checking that notifications are cleared.'); - }); -}); diff --git a/src/pybind/mgr/dashboard/frontend/e2e/ui/notification.po.ts b/src/pybind/mgr/dashboard/frontend/e2e/ui/notification.po.ts deleted file mode 100644 index 68eeb61e919e..000000000000 --- a/src/pybind/mgr/dashboard/frontend/e2e/ui/notification.po.ts +++ /dev/null @@ -1,34 +0,0 @@ -import { by, element } from 'protractor'; - -import { PageHelper } from '../page-helper.po'; - -export class NotificationSidebarPageHelper extends PageHelper { - getNotificatinoIcon() { - return element(by.css('cd-notifications a')); - } - - getSidebar() { - return element(by.css('cd-notifications-sidebar')); - } - - getTasks() { - return this.getSidebar().all(by.css('.card.tc_task')); - } - - getNotifications() { - return this.getSidebar().all(by.css('.card.tc_notification')); - } - - getClearNotficationsBtn() { - return this.getSidebar().element(by.css('button.btn-block')); - } - - getCloseBtn() { - return this.getSidebar().element(by.css('button.close')); - } - - async open() { - await this.waitClickableAndClick(this.getNotificatinoIcon()); - return this.waitVisibility(this.getSidebar()); - } -} diff --git a/src/pybind/mgr/dashboard/frontend/e2e/ui/role-mgmt.e2e-spec.ts b/src/pybind/mgr/dashboard/frontend/e2e/ui/role-mgmt.e2e-spec.ts deleted file mode 100644 index badb3c968e33..000000000000 --- a/src/pybind/mgr/dashboard/frontend/e2e/ui/role-mgmt.e2e-spec.ts +++ /dev/null @@ -1,41 +0,0 @@ -import { RoleMgmtPageHelper } from './role-mgmt.po'; - -describe('Role Management page', () => { - let roleMgmt: RoleMgmtPageHelper; - const role_name = 'user_mgmt_create_edit_delete_role'; - - beforeAll(() => { - roleMgmt = new RoleMgmtPageHelper(); - }); - - afterEach(async () => { - await RoleMgmtPageHelper.checkConsole(); - }); - - describe('breadcrumb tests', () => { - it('should check breadcrumb on roles tab on user management page', async () => { - await roleMgmt.navigateTo(); - await roleMgmt.waitTextToBePresent(roleMgmt.getBreadcrumb(), 'Roles'); - }); - - it('should check breadcrumb on role creation page', async () => { - await roleMgmt.navigateTo('create'); - await roleMgmt.waitTextToBePresent(roleMgmt.getBreadcrumb(), 'Create'); - }); - }); - - describe('role create, edit & delete test', () => { - it('should create a role', async () => { - await roleMgmt.create(role_name, 'An interesting description'); - }); - - it('should edit a role', async () => { - await roleMgmt.edit(role_name, 'A far more interesting description'); - }); - - it('should delete a role', async () => { - await roleMgmt.navigateTo(); - await roleMgmt.delete(role_name); - }); - }); -}); diff --git a/src/pybind/mgr/dashboard/frontend/e2e/ui/role-mgmt.po.ts b/src/pybind/mgr/dashboard/frontend/e2e/ui/role-mgmt.po.ts deleted file mode 100644 index dbec067ed0a7..000000000000 --- a/src/pybind/mgr/dashboard/frontend/e2e/ui/role-mgmt.po.ts +++ /dev/null @@ -1,41 +0,0 @@ -import { by, element } from 'protractor'; -import { PageHelper } from '../page-helper.po'; - -export class RoleMgmtPageHelper extends PageHelper { - pages = { - index: '/#/user-management/roles', - create: '/#/user-management/roles/create' - }; - - async create(name: string, description: string): Promise { - await this.navigateTo('create'); - - // fill in fields - await element(by.id('name')).sendKeys(name); - await element(by.id('description')).sendKeys(description); - - // Click the create button and wait for role to be made - const createButton = element(by.cssContainingText('button', 'Create Role')); - await createButton.click(); - - await this.waitPresence(this.getFirstTableCellWithText(name)); - } - - async edit(name: string, description: string): Promise { - await this.navigateTo(); - - await this.getFirstTableCellWithText(name).click(); // select role from table - await element(by.cssContainingText('button', 'Edit')).click(); // click button to move to edit page - - // fill in fields with new values - await element(by.id('description')).clear(); - await element(by.id('description')).sendKeys(description); - - // Click the edit button and check new values are present in table - const editButton = element(by.cssContainingText('button', 'Edit Role')); - await editButton.click(); - - await this.waitPresence(this.getFirstTableCellWithText(name)); - await this.waitPresence(this.getFirstTableCellWithText(description)); - } -} diff --git a/src/pybind/mgr/dashboard/frontend/e2e/ui/user-mgmt.e2e-spec.ts b/src/pybind/mgr/dashboard/frontend/e2e/ui/user-mgmt.e2e-spec.ts deleted file mode 100644 index f40ce1f10a8c..000000000000 --- a/src/pybind/mgr/dashboard/frontend/e2e/ui/user-mgmt.e2e-spec.ts +++ /dev/null @@ -1,41 +0,0 @@ -import { UserMgmtPageHelper } from './user-mgmt.po'; - -describe('User Management page', () => { - let userMgmt: UserMgmtPageHelper; - const user_name = 'user_mgmt_create_edit_delete_user'; - - beforeAll(() => { - userMgmt = new UserMgmtPageHelper(); - }); - - afterEach(async () => { - await UserMgmtPageHelper.checkConsole(); - }); - - describe('breadcrumb tests', () => { - it('should check breadcrumb on users tab of user management page', async () => { - await userMgmt.navigateTo(); - await userMgmt.waitTextToBePresent(userMgmt.getBreadcrumb(), 'Users'); - }); - - it('should check breadcrumb on user creation page', async () => { - await userMgmt.navigateTo('create'); - await userMgmt.waitTextToBePresent(userMgmt.getBreadcrumb(), 'Create'); - }); - }); - - describe('user create, edit & delete test', () => { - it('should create a user', async () => { - await userMgmt.create(user_name, 'cool_password', 'Jeff', 'realemail@realwebsite.com'); - }); - - it('should edit a user', async () => { - await userMgmt.edit(user_name, 'cool_password_number_2', 'Geoff', 'w@m'); - }); - - it('should delete a user', async () => { - await userMgmt.navigateTo(); - await userMgmt.delete(user_name); - }); - }); -}); diff --git a/src/pybind/mgr/dashboard/frontend/e2e/ui/user-mgmt.po.ts b/src/pybind/mgr/dashboard/frontend/e2e/ui/user-mgmt.po.ts deleted file mode 100644 index d59eddb39af9..000000000000 --- a/src/pybind/mgr/dashboard/frontend/e2e/ui/user-mgmt.po.ts +++ /dev/null @@ -1,48 +0,0 @@ -import { by, element } from 'protractor'; -import { PageHelper } from '../page-helper.po'; - -export class UserMgmtPageHelper extends PageHelper { - pages = { - index: '/#/user-management/users', - create: '/#/user-management/users/create' - }; - - async create(username: string, password: string, name: string, email: string): Promise { - await this.navigateTo('create'); - - // fill in fields - await element(by.id('username')).sendKeys(username); - await element(by.id('password')).sendKeys(password); - await element(by.id('confirmpassword')).sendKeys(password); - await element(by.id('name')).sendKeys(name); - await element(by.id('email')).sendKeys(email); - - // Click the create button and wait for user to be made - const createButton = element(by.cssContainingText('button', 'Create User')); - await createButton.click(); - await this.waitPresence(this.getFirstTableCellWithText(username)); - } - - async edit(username: string, password: string, name: string, email: string): Promise { - await this.navigateTo(); - - await this.getFirstTableCellWithText(username).click(); // select user from table - await element(by.cssContainingText('button', 'Edit')).click(); // click button to move to edit page - - // fill in fields with new values - await element(by.id('password')).clear(); - await element(by.id('password')).sendKeys(password); - await element(by.id('confirmpassword')).clear(); - await element(by.id('confirmpassword')).sendKeys(password); - await element(by.id('name')).clear(); - await element(by.id('name')).sendKeys(name); - await element(by.id('email')).clear(); - await element(by.id('email')).sendKeys(email); - - // Click the edit button and check new values are present in table - const editButton = element(by.cssContainingText('button', 'Edit User')); - await editButton.click(); - await this.waitPresence(this.getFirstTableCellWithText(email)); - await this.waitPresence(this.getFirstTableCellWithText(name)); - } -} diff --git a/src/pybind/mgr/dashboard/frontend/package-lock.json b/src/pybind/mgr/dashboard/frontend/package-lock.json index 4dc78438ac1d..139d7887769e 100644 --- a/src/pybind/mgr/dashboard/frontend/package-lock.json +++ b/src/pybind/mgr/dashboard/frontend/package-lock.json @@ -89,6 +89,12 @@ "worker-plugin": "3.2.0" }, "dependencies": { + "core-js": { + "version": "3.6.4", + "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.6.4.tgz", + "integrity": "sha512-4paDGScNgZP2IXXilaffL9X7968RuvwlkK3xWtZRVqgd8SYNiVKRJvkFd1aqqEuPfN7E68ZHEp9hDj6lHj4Hyw==", + "dev": true + }, "glob": { "version": "7.1.4", "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.4.tgz", @@ -296,7 +302,7 @@ "convert-source-map": "^1.5.1", "dependency-graph": "^0.7.2", "magic-string": "^0.25.0", - "minimist": "1.2.5", + "minimist": "^1.2.0", "reflect-metadata": "^0.1.2", "source-map": "^0.6.1", "tslib": "^1.9.0", @@ -696,9 +702,10 @@ } }, "minimist": { + "version": "1.2.5", + "bundled": true, "dev": true, - "optional": true, - "version": "1.2.5" + "optional": true }, "minipass": { "version": "2.9.0", @@ -725,12 +732,7 @@ "dev": true, "optional": true, "requires": { - "minimist": "1.2.5" - }, - "dependencies": { - "minimist": { - "version": "1.2.5" - } + "minimist": "^1.2.5" } }, "ms": { @@ -879,13 +881,8 @@ "requires": { "deep-extend": "^0.6.0", "ini": "~1.3.0", - "minimist": "1.2.5", + "minimist": "^1.2.0", "strip-json-comments": "~2.0.1" - }, - "dependencies": { - "minimist": { - "version": "1.2.5" - } } }, "readable-stream": { @@ -1116,9 +1113,6 @@ "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", "dev": true }, - "minimist": { - "version": "1.2.5" - }, "os-locale": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/os-locale/-/os-locale-3.1.0.tgz", @@ -1136,9 +1130,9 @@ } }, "p-limit": { - "version": "2.2.2", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.2.2.tgz", - "integrity": "sha512-WGR+xHecKTr7EbUEhyLSh5Dube9JtdiG78ufaeLxTgpudf/20KqyMioIUZJAezlTIi6evxuoUs9YXc11cU+yzQ==", + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", "dev": true, "requires": { "p-try": "^2.0.0" @@ -1378,17 +1372,12 @@ }, "dependencies": { "json5": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/json5/-/json5-2.1.2.tgz", - "integrity": "sha512-MoUOQ4WdiN3yxhm7NEVJSJrieAo5hNSLQ5sj05OTRHPL9HOBy8u4Bu88jsC1jvqAdN+E1bJmsUcZH+1HQxliqQ==", + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.1.3.tgz", + "integrity": "sha512-KXPvOm8K9IJKFM0bmdn8QXh7udDh1g/giieX0NLCaMnb4hEiVFqnop2ImTXCc5e0/oHz3LTqmHGtExn5hfMkOA==", "dev": true, "requires": { - "minimist": "1.2.5" - }, - "dependencies": { - "minimist": { - "version": "1.2.5" - } + "minimist": "^1.2.5" } }, "semver": { @@ -1406,12 +1395,12 @@ } }, "@babel/generator": { - "version": "7.9.4", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.9.4.tgz", - "integrity": "sha512-rjP8ahaDy/ouhrvCoU1E5mqaitWrxwuNGU+dy1EpaoK48jZay4MdkskKGIMHLZNewg8sAsqpGSREJwP0zH3YQA==", + "version": "7.9.5", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.9.5.tgz", + "integrity": "sha512-GbNIxVB3ZJe3tLeDm1HSn2AhuD/mVcyLDpgtLXa5tplmWrJdF/elxB56XNqCuD6szyNkDi6wuoKXln3QeBmCHQ==", "dev": true, "requires": { - "@babel/types": "^7.9.0", + "@babel/types": "^7.9.5", "jsesc": "^2.5.1", "lodash": "^4.17.13", "source-map": "^0.5.0" @@ -1498,14 +1487,14 @@ } }, "@babel/helper-function-name": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.8.3.tgz", - "integrity": "sha512-BCxgX1BC2hD/oBlIFUgOCQDOPV8nSINxCwM3o93xP4P9Fq6aV5sgv2cOOITDMtCfQ+3PvHp3l689XZvAM9QyOA==", + "version": "7.9.5", + "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.9.5.tgz", + "integrity": "sha512-JVcQZeXM59Cd1qanDUxv9fgJpt3NeKUaqBqUEvfmQ+BCOKq2xUgaWZW2hr0dkbyJgezYuplEoh5knmrnS68efw==", "dev": true, "requires": { "@babel/helper-get-function-arity": "^7.8.3", "@babel/template": "^7.8.3", - "@babel/types": "^7.8.3" + "@babel/types": "^7.9.5" } }, "@babel/helper-get-function-arity": { @@ -1628,9 +1617,9 @@ } }, "@babel/helper-validator-identifier": { - "version": "7.9.0", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.9.0.tgz", - "integrity": "sha512-6G8bQKjOh+of4PV/ThDm/rRqlU7+IGoJuofpagU5GlEl29Vv0RGqqt86ZGRV8ZuSOY3o+8yXl5y782SMcG7SHw==", + "version": "7.9.5", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.9.5.tgz", + "integrity": "sha512-/8arLKUFq882w4tWGj9JYzRpAlZgiWUJ+dtteNTDqrRBz9Iguck9Rn3ykuBDoUwh2TO4tSAJlrxDUOXWklJe4g==", "dev": true }, "@babel/helper-wrap-function": { @@ -1715,13 +1704,14 @@ } }, "@babel/plugin-proposal-object-rest-spread": { - "version": "7.9.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-object-rest-spread/-/plugin-proposal-object-rest-spread-7.9.0.tgz", - "integrity": "sha512-UgqBv6bjq4fDb8uku9f+wcm1J7YxJ5nT7WO/jBr0cl0PLKb7t1O6RNR1kZbjgx2LQtsDI9hwoQVmn0yhXeQyow==", + "version": "7.9.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-object-rest-spread/-/plugin-proposal-object-rest-spread-7.9.5.tgz", + "integrity": "sha512-VP2oXvAf7KCYTthbUHwBlewbl1Iq059f6seJGsxMizaCdgHIeczOr7FBqELhSqfkIl04Fi8okzWzl63UKbQmmg==", "dev": true, "requires": { "@babel/helper-plugin-utils": "^7.8.3", - "@babel/plugin-syntax-object-rest-spread": "^7.8.0" + "@babel/plugin-syntax-object-rest-spread": "^7.8.0", + "@babel/plugin-transform-parameters": "^7.9.5" } }, "@babel/plugin-proposal-optional-catch-binding": { @@ -1772,6 +1762,15 @@ "@babel/helper-plugin-utils": "^7.8.0" } }, + "@babel/plugin-syntax-class-properties": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-class-properties/-/plugin-syntax-class-properties-7.8.3.tgz", + "integrity": "sha512-UcAyQWg2bAN647Q+O811tG9MrJ38Z10jjhQdKNAL8fsyPzE3cCN/uT+f55cFVY4aGO4jqJAvmqsuY3GQDwAoXg==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.8.3" + } + }, "@babel/plugin-syntax-dynamic-import": { "version": "7.8.3", "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-dynamic-import/-/plugin-syntax-dynamic-import-7.8.3.tgz", @@ -1790,6 +1789,15 @@ "@babel/helper-plugin-utils": "^7.8.0" } }, + "@babel/plugin-syntax-logical-assignment-operators": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-logical-assignment-operators/-/plugin-syntax-logical-assignment-operators-7.8.3.tgz", + "integrity": "sha512-Zpg2Sgc++37kuFl6ppq2Q7Awc6E6AIW671x5PY8E/f7MCIyPPGK/EoeZXvvY3P42exZ3Q4/t3YOzP/HiN79jDg==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.8.3" + } + }, "@babel/plugin-syntax-nullish-coalescing-operator": { "version": "7.8.3", "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-nullish-coalescing-operator/-/plugin-syntax-nullish-coalescing-operator-7.8.3.tgz", @@ -1799,6 +1807,15 @@ "@babel/helper-plugin-utils": "^7.8.0" } }, + "@babel/plugin-syntax-numeric-separator": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-numeric-separator/-/plugin-syntax-numeric-separator-7.8.3.tgz", + "integrity": "sha512-H7dCMAdN83PcCmqmkHB5dtp+Xa9a6LKSvA2hiFBC/5alSHxM5VgWZXFqDi0YFe8XNGT6iCa+z4V4zSt/PdZ7Dw==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.8.3" + } + }, "@babel/plugin-syntax-object-rest-spread": { "version": "7.8.3", "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-object-rest-spread/-/plugin-syntax-object-rest-spread-7.8.3.tgz", @@ -1875,14 +1892,14 @@ } }, "@babel/plugin-transform-classes": { - "version": "7.9.2", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-classes/-/plugin-transform-classes-7.9.2.tgz", - "integrity": "sha512-TC2p3bPzsfvSsqBZo0kJnuelnoK9O3welkUpqSqBQuBF6R5MN2rysopri8kNvtlGIb2jmUO7i15IooAZJjZuMQ==", + "version": "7.9.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-classes/-/plugin-transform-classes-7.9.5.tgz", + "integrity": "sha512-x2kZoIuLC//O5iA7PEvecB105o7TLzZo8ofBVhP79N+DO3jaX+KYfww9TQcfBEZD0nikNyYcGB1IKtRq36rdmg==", "dev": true, "requires": { "@babel/helper-annotate-as-pure": "^7.8.3", "@babel/helper-define-map": "^7.8.3", - "@babel/helper-function-name": "^7.8.3", + "@babel/helper-function-name": "^7.9.5", "@babel/helper-optimise-call-expression": "^7.8.3", "@babel/helper-plugin-utils": "^7.8.3", "@babel/helper-replace-supers": "^7.8.6", @@ -1900,9 +1917,9 @@ } }, "@babel/plugin-transform-destructuring": { - "version": "7.8.8", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.8.8.tgz", - "integrity": "sha512-eRJu4Vs2rmttFCdhPUM3bV0Yo/xPSdPw6ML9KHs/bjB4bLA5HXlbvYXPOD5yASodGod+krjYx21xm1QmL8dCJQ==", + "version": "7.9.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.9.5.tgz", + "integrity": "sha512-j3OEsGel8nHL/iusv/mRd5fYZ3DrOxWC82x0ogmdN/vHfAP4MYw+AFKYanzWlktNwikKvlzUV//afBW5FTp17Q==", "dev": true, "requires": { "@babel/helper-plugin-utils": "^7.8.3" @@ -2048,9 +2065,9 @@ } }, "@babel/plugin-transform-parameters": { - "version": "7.9.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.9.3.tgz", - "integrity": "sha512-fzrQFQhp7mIhOzmOtPiKffvCYQSK10NR8t6BBz2yPbeUHb9OLW8RZGtgDRBn8z2hGcwvKDL3vC7ojPTLNxmqEg==", + "version": "7.9.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.9.5.tgz", + "integrity": "sha512-0+1FhHnMfj6lIIhVvS4KGQJeuhe1GI//h5uptK4PvLt+BGBxsoUJbd3/IW002yk//6sZPlFgsG1hY6OHLcy6kA==", "dev": true, "requires": { "@babel/helper-get-function-arity": "^7.8.3", @@ -2243,29 +2260,29 @@ } }, "@babel/traverse": { - "version": "7.9.0", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.9.0.tgz", - "integrity": "sha512-jAZQj0+kn4WTHO5dUZkZKhbFrqZE7K5LAQ5JysMnmvGij+wOdr+8lWqPeW0BcF4wFwrEXXtdGO7wcV6YPJcf3w==", + "version": "7.9.5", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.9.5.tgz", + "integrity": "sha512-c4gH3jsvSuGUezlP6rzSJ6jf8fYjLj3hsMZRx/nX0h+fmHN0w+ekubRrHPqnMec0meycA2nwCsJ7dC8IPem2FQ==", "dev": true, "requires": { "@babel/code-frame": "^7.8.3", - "@babel/generator": "^7.9.0", - "@babel/helper-function-name": "^7.8.3", + "@babel/generator": "^7.9.5", + "@babel/helper-function-name": "^7.9.5", "@babel/helper-split-export-declaration": "^7.8.3", "@babel/parser": "^7.9.0", - "@babel/types": "^7.9.0", + "@babel/types": "^7.9.5", "debug": "^4.1.0", "globals": "^11.1.0", "lodash": "^4.17.13" } }, "@babel/types": { - "version": "7.9.0", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.9.0.tgz", - "integrity": "sha512-BS9JKfXkzzJl8RluW4JGknzpiUV7ZrvTayM6yfqLTVBEnFtyowVIOu6rqxRd5cVO6yGoWf4T8u8dgK9oB+GCng==", + "version": "7.9.5", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.9.5.tgz", + "integrity": "sha512-XjnvNqenk818r5zMaba+sLQjnbda31UfUURv3ei0qPQw4u+j2jMyJ5b11y8ZHYTRSI3NnInQkkkRT4fLqqPdHg==", "dev": true, "requires": { - "@babel/helper-validator-identifier": "^7.9.0", + "@babel/helper-validator-identifier": "^7.9.5", "lodash": "^4.17.13", "to-fast-properties": "^2.0.0" } @@ -2283,12 +2300,7 @@ "dev": true, "requires": { "exec-sh": "^0.3.2", - "minimist": "1.2.5" - }, - "dependencies": { - "minimist": { - "version": "1.2.5" - } + "minimist": "^1.2.0" } }, "@compodoc/compodoc": { @@ -2320,7 +2332,7 @@ "loglevel-plugin-prefix": "^0.8.4", "lunr": "^2.3.6", "marked": "^0.7.0", - "minimist": "1.2.5", + "minimist": "^1.2.0", "opencollective-postinstall": "^2.0.2", "os-name": "^3.1.0", "pdfmake": "^0.1.60", @@ -2351,21 +2363,13 @@ } }, "json5": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/json5/-/json5-2.1.2.tgz", - "integrity": "sha512-MoUOQ4WdiN3yxhm7NEVJSJrieAo5hNSLQ5sj05OTRHPL9HOBy8u4Bu88jsC1jvqAdN+E1bJmsUcZH+1HQxliqQ==", + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.1.3.tgz", + "integrity": "sha512-KXPvOm8K9IJKFM0bmdn8QXh7udDh1g/giieX0NLCaMnb4hEiVFqnop2ImTXCc5e0/oHz3LTqmHGtExn5hfMkOA==", "dev": true, "requires": { - "minimist": "1.2.5" - }, - "dependencies": { - "minimist": { - "version": "1.2.5" - } + "minimist": "^1.2.5" } - }, - "minimist": { - "version": "1.2.5" } } }, @@ -2422,6 +2426,129 @@ } } }, + "@cypress/listr-verbose-renderer": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/@cypress/listr-verbose-renderer/-/listr-verbose-renderer-0.4.1.tgz", + "integrity": "sha1-p3SS9LEdzHxEajSz4ochr9M8ZCo=", + "dev": true, + "requires": { + "chalk": "^1.1.3", + "cli-cursor": "^1.0.2", + "date-fns": "^1.27.2", + "figures": "^1.7.0" + }, + "dependencies": { + "ansi-styles": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz", + "integrity": "sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4=", + "dev": true + }, + "chalk": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", + "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=", + "dev": true, + "requires": { + "ansi-styles": "^2.2.1", + "escape-string-regexp": "^1.0.2", + "has-ansi": "^2.0.0", + "strip-ansi": "^3.0.0", + "supports-color": "^2.0.0" + } + }, + "cli-cursor": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-1.0.2.tgz", + "integrity": "sha1-ZNo/fValRBLll5S9Ytw1KV6PKYc=", + "dev": true, + "requires": { + "restore-cursor": "^1.0.1" + } + }, + "figures": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/figures/-/figures-1.7.0.tgz", + "integrity": "sha1-y+Hjr/zxzUS4DK3+0o3Hk6lwHS4=", + "dev": true, + "requires": { + "escape-string-regexp": "^1.0.5", + "object-assign": "^4.1.0" + } + }, + "onetime": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-1.1.0.tgz", + "integrity": "sha1-ofeDj4MUxRbwXs78vEzP4EtO14k=", + "dev": true + }, + "restore-cursor": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-1.0.1.tgz", + "integrity": "sha1-NGYfRohjJ/7SmRR5FSJS35LapUE=", + "dev": true, + "requires": { + "exit-hook": "^1.0.0", + "onetime": "^1.0.0" + } + }, + "supports-color": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz", + "integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=", + "dev": true + } + } + }, + "@cypress/request": { + "version": "2.88.5", + "resolved": "https://registry.npmjs.org/@cypress/request/-/request-2.88.5.tgz", + "integrity": "sha512-TzEC1XMi1hJkywWpRfD2clreTa/Z+lOrXDCxxBTBPEcY5azdPi56A6Xw+O4tWJnaJH3iIE7G5aDXZC6JgRZLcA==", + "dev": true, + "requires": { + "aws-sign2": "~0.7.0", + "aws4": "^1.8.0", + "caseless": "~0.12.0", + "combined-stream": "~1.0.6", + "extend": "~3.0.2", + "forever-agent": "~0.6.1", + "form-data": "~2.3.2", + "har-validator": "~5.1.3", + "http-signature": "~1.2.0", + "is-typedarray": "~1.0.0", + "isstream": "~0.1.2", + "json-stringify-safe": "~5.0.1", + "mime-types": "~2.1.19", + "oauth-sign": "~0.9.0", + "performance-now": "^2.1.0", + "qs": "~6.5.2", + "safe-buffer": "^5.1.2", + "tough-cookie": "~2.5.0", + "tunnel-agent": "^0.6.0", + "uuid": "^3.3.2" + } + }, + "@cypress/xvfb": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@cypress/xvfb/-/xvfb-1.2.4.tgz", + "integrity": "sha512-skbBzPggOVYCbnGgV+0dmBdW/s77ZkAOXIC1knS8NagwDjBrNC1LuXtQJeiN6l+m7lzmHtaoUw/ctJKdqkG57Q==", + "dev": true, + "requires": { + "debug": "^3.1.0", + "lodash.once": "^4.1.1" + }, + "dependencies": { + "debug": { + "version": "3.2.6", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.6.tgz", + "integrity": "sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==", + "dev": true, + "requires": { + "ms": "^2.1.1" + } + } + } + }, "@dsherret/to-absolute-glob": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/@dsherret/to-absolute-glob/-/to-absolute-glob-2.0.2.tgz", @@ -2432,6 +2559,52 @@ "is-negated-glob": "^1.0.0" } }, + "@hapi/address": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/@hapi/address/-/address-2.1.4.tgz", + "integrity": "sha512-QD1PhQk+s31P1ixsX0H0Suoupp3VMXzIVMSwobR3F3MSUO2YCV0B7xqLcUw/Bh8yuvd3LhpyqLQWTNcRmp6IdQ==", + "dev": true + }, + "@hapi/formula": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@hapi/formula/-/formula-1.2.0.tgz", + "integrity": "sha512-UFbtbGPjstz0eWHb+ga/GM3Z9EzqKXFWIbSOFURU0A/Gku0Bky4bCk9/h//K2Xr3IrCfjFNhMm4jyZ5dbCewGA==", + "dev": true + }, + "@hapi/hoek": { + "version": "8.5.1", + "resolved": "https://registry.npmjs.org/@hapi/hoek/-/hoek-8.5.1.tgz", + "integrity": "sha512-yN7kbciD87WzLGc5539Tn0sApjyiGHAJgKvG9W8C7O+6c7qmoQMfVs0W4bX17eqz6C78QJqqFrtgdK5EWf6Qow==", + "dev": true + }, + "@hapi/joi": { + "version": "16.1.8", + "resolved": "https://registry.npmjs.org/@hapi/joi/-/joi-16.1.8.tgz", + "integrity": "sha512-wAsVvTPe+FwSrsAurNt5vkg3zo+TblvC5Bb1zMVK6SJzZqw9UrJnexxR+76cpePmtUZKHAPxcQ2Bf7oVHyahhg==", + "dev": true, + "requires": { + "@hapi/address": "^2.1.2", + "@hapi/formula": "^1.2.0", + "@hapi/hoek": "^8.2.4", + "@hapi/pinpoint": "^1.0.2", + "@hapi/topo": "^3.1.3" + } + }, + "@hapi/pinpoint": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@hapi/pinpoint/-/pinpoint-1.0.2.tgz", + "integrity": "sha512-dtXC/WkZBfC5vxscazuiJ6iq4j9oNx1SHknmIr8hofarpKUZKmlUVYVIhNVzIEgK5Wrc4GMHL5lZtt1uS2flmQ==", + "dev": true + }, + "@hapi/topo": { + "version": "3.1.6", + "resolved": "https://registry.npmjs.org/@hapi/topo/-/topo-3.1.6.tgz", + "integrity": "sha512-tAag0jEcjwH+P2quUfipd7liWCNX2F8NvYjQp2wtInsZxnMlypdw0FtAOLxtvvkO+GSRRbmNi8m/5y42PQJYCQ==", + "dev": true, + "requires": { + "@hapi/hoek": "^8.3.0" + } + }, "@istanbuljs/load-nyc-config": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/@istanbuljs/load-nyc-config/-/load-nyc-config-1.0.0.tgz", @@ -2470,9 +2643,9 @@ } }, "p-limit": { - "version": "2.2.2", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.2.2.tgz", - "integrity": "sha512-WGR+xHecKTr7EbUEhyLSh5Dube9JtdiG78ufaeLxTgpudf/20KqyMioIUZJAezlTIi6evxuoUs9YXc11cU+yzQ==", + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", "dev": true, "requires": { "p-try": "^2.0.0" @@ -2514,14 +2687,15 @@ "dev": true }, "@jest/console": { - "version": "25.2.3", - "resolved": "https://registry.npmjs.org/@jest/console/-/console-25.2.3.tgz", - "integrity": "sha512-k+37B1aSvOt9tKHWbZZSOy1jdgzesB0bj96igCVUG1nAH1W5EoUfgc5EXbBVU08KSLvkVdWopLXaO3xfVGlxtQ==", + "version": "25.4.0", + "resolved": "https://registry.npmjs.org/@jest/console/-/console-25.4.0.tgz", + "integrity": "sha512-CfE0erx4hdJ6t7RzAcE1wLG6ZzsHSmybvIBQDoCkDM1QaSeWL9wJMzID/2BbHHa7ll9SsbbK43HjbERbBaFX2A==", "dev": true, "requires": { - "@jest/source-map": "^25.2.1", + "@jest/types": "^25.4.0", "chalk": "^3.0.0", - "jest-util": "^25.2.3", + "jest-message-util": "^25.4.0", + "jest-util": "^25.4.0", "slash": "^3.0.0" }, "dependencies": { @@ -2578,33 +2752,33 @@ } }, "@jest/core": { - "version": "25.2.4", - "resolved": "https://registry.npmjs.org/@jest/core/-/core-25.2.4.tgz", - "integrity": "sha512-WcWYShl0Bqfcb32oXtjwbiR78D/djhMdJW+ulp4/bmHgeODcsieqUJfUH+kEv8M7VNV77E6jds5aA+WuGh1nmg==", + "version": "25.4.0", + "resolved": "https://registry.npmjs.org/@jest/core/-/core-25.4.0.tgz", + "integrity": "sha512-h1x9WSVV0+TKVtATGjyQIMJENs8aF6eUjnCoi4jyRemYZmekLr8EJOGQqTWEX8W6SbZ6Skesy9pGXrKeAolUJw==", "dev": true, "requires": { - "@jest/console": "^25.2.3", - "@jest/reporters": "^25.2.4", - "@jest/test-result": "^25.2.4", - "@jest/transform": "^25.2.4", - "@jest/types": "^25.2.3", + "@jest/console": "^25.4.0", + "@jest/reporters": "^25.4.0", + "@jest/test-result": "^25.4.0", + "@jest/transform": "^25.4.0", + "@jest/types": "^25.4.0", "ansi-escapes": "^4.2.1", "chalk": "^3.0.0", "exit": "^0.1.2", "graceful-fs": "^4.2.3", - "jest-changed-files": "^25.2.3", - "jest-config": "^25.2.4", - "jest-haste-map": "^25.2.3", - "jest-message-util": "^25.2.4", - "jest-regex-util": "^25.2.1", - "jest-resolve": "^25.2.3", - "jest-resolve-dependencies": "^25.2.4", - "jest-runner": "^25.2.4", - "jest-runtime": "^25.2.4", - "jest-snapshot": "^25.2.4", - "jest-util": "^25.2.3", - "jest-validate": "^25.2.3", - "jest-watcher": "^25.2.4", + "jest-changed-files": "^25.4.0", + "jest-config": "^25.4.0", + "jest-haste-map": "^25.4.0", + "jest-message-util": "^25.4.0", + "jest-regex-util": "^25.2.6", + "jest-resolve": "^25.4.0", + "jest-resolve-dependencies": "^25.4.0", + "jest-runner": "^25.4.0", + "jest-runtime": "^25.4.0", + "jest-snapshot": "^25.4.0", + "jest-util": "^25.4.0", + "jest-validate": "^25.4.0", + "jest-watcher": "^25.4.0", "micromatch": "^4.0.2", "p-each-series": "^2.1.0", "realpath-native": "^2.0.0", @@ -2714,40 +2888,40 @@ } }, "@jest/environment": { - "version": "25.2.4", - "resolved": "https://registry.npmjs.org/@jest/environment/-/environment-25.2.4.tgz", - "integrity": "sha512-wA4xlhD19/gukkDpJ5HQsTle0pgnzI5qMFEjw267lpTDC8d9N7Ihqr5pI+l0p8Qn1SQhai+glSqxrGdzKy4jxw==", + "version": "25.4.0", + "resolved": "https://registry.npmjs.org/@jest/environment/-/environment-25.4.0.tgz", + "integrity": "sha512-KDctiak4mu7b4J6BIoN/+LUL3pscBzoUCP+EtSPd2tK9fqyDY5OF+CmkBywkFWezS9tyH5ACOQNtpjtueEDH6Q==", "dev": true, "requires": { - "@jest/fake-timers": "^25.2.4", - "@jest/types": "^25.2.3", - "jest-mock": "^25.2.3" + "@jest/fake-timers": "^25.4.0", + "@jest/types": "^25.4.0", + "jest-mock": "^25.4.0" } }, "@jest/fake-timers": { - "version": "25.2.4", - "resolved": "https://registry.npmjs.org/@jest/fake-timers/-/fake-timers-25.2.4.tgz", - "integrity": "sha512-oC1TJiwfMcBttVN7Wz+VZnqEAgYTiEMu0QLOXpypR89nab0uCB31zm/QeBZddhSstn20qe3yqOXygp6OwvKT/Q==", + "version": "25.4.0", + "resolved": "https://registry.npmjs.org/@jest/fake-timers/-/fake-timers-25.4.0.tgz", + "integrity": "sha512-lI9z+VOmVX4dPPFzyj0vm+UtaB8dCJJ852lcDnY0uCPRvZAaVGnMwBBc1wxtf+h7Vz6KszoOvKAt4QijDnHDkg==", "dev": true, "requires": { - "@jest/types": "^25.2.3", - "jest-message-util": "^25.2.4", - "jest-mock": "^25.2.3", - "jest-util": "^25.2.3", + "@jest/types": "^25.4.0", + "jest-message-util": "^25.4.0", + "jest-mock": "^25.4.0", + "jest-util": "^25.4.0", "lolex": "^5.0.0" } }, "@jest/reporters": { - "version": "25.2.4", - "resolved": "https://registry.npmjs.org/@jest/reporters/-/reporters-25.2.4.tgz", - "integrity": "sha512-VHbLxM03jCc+bTLOluW/IqHR2G0Cl0iATwIQbuZtIUast8IXO4fD0oy4jpVGpG5b20S6REA8U3BaQoCW/CeVNQ==", + "version": "25.4.0", + "resolved": "https://registry.npmjs.org/@jest/reporters/-/reporters-25.4.0.tgz", + "integrity": "sha512-bhx/buYbZgLZm4JWLcRJ/q9Gvmd3oUh7k2V7gA4ZYBx6J28pIuykIouclRdiAC6eGVX1uRZT+GK4CQJLd/PwPg==", "dev": true, "requires": { "@bcoe/v8-coverage": "^0.2.3", - "@jest/console": "^25.2.3", - "@jest/test-result": "^25.2.4", - "@jest/transform": "^25.2.4", - "@jest/types": "^25.2.3", + "@jest/console": "^25.4.0", + "@jest/test-result": "^25.4.0", + "@jest/transform": "^25.4.0", + "@jest/types": "^25.4.0", "chalk": "^3.0.0", "collect-v8-coverage": "^1.0.0", "exit": "^0.1.2", @@ -2756,17 +2930,17 @@ "istanbul-lib-instrument": "^4.0.0", "istanbul-lib-report": "^3.0.0", "istanbul-lib-source-maps": "^4.0.0", - "istanbul-reports": "^3.0.0", - "jest-haste-map": "^25.2.3", - "jest-resolve": "^25.2.3", - "jest-util": "^25.2.3", - "jest-worker": "^25.2.1", + "istanbul-reports": "^3.0.2", + "jest-haste-map": "^25.4.0", + "jest-resolve": "^25.4.0", + "jest-util": "^25.4.0", + "jest-worker": "^25.4.0", "node-notifier": "^6.0.0", "slash": "^3.0.0", "source-map": "^0.6.0", "string-length": "^3.1.0", "terminal-link": "^2.0.0", - "v8-to-istanbul": "^4.0.1" + "v8-to-istanbul": "^4.1.3" }, "dependencies": { "ansi-styles": { @@ -2805,9 +2979,9 @@ "dev": true }, "jest-worker": { - "version": "25.2.1", - "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-25.2.1.tgz", - "integrity": "sha512-IHnpekk8H/hCUbBlfeaPZzU6v75bqwJp3n4dUrQuQOAgOneI4tx3jV2o8pvlXnDfcRsfkFIUD//HWXpCmR+evQ==", + "version": "25.4.0", + "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-25.4.0.tgz", + "integrity": "sha512-ghAs/1FtfYpMmYQ0AHqxV62XPvKdUDIBBApMZfly+E9JEmYh2K45G0R5dWxx986RN12pRCxsViwQVtGl+N4whw==", "dev": true, "requires": { "merge-stream": "^2.0.0", @@ -2838,9 +3012,9 @@ } }, "@jest/source-map": { - "version": "25.2.1", - "resolved": "https://registry.npmjs.org/@jest/source-map/-/source-map-25.2.1.tgz", - "integrity": "sha512-PgScGJm1U27+9Te/cxP4oUFqJ2PX6NhBL2a6unQ7yafCgs8k02c0LSyjSIx/ao0AwcAdCczfAPDf5lJ7zoB/7A==", + "version": "25.2.6", + "resolved": "https://registry.npmjs.org/@jest/source-map/-/source-map-25.2.6.tgz", + "integrity": "sha512-VuIRZF8M2zxYFGTEhkNSvQkUKafQro4y+mwUxy5ewRqs5N/ynSFUODYp3fy1zCnbCMy1pz3k+u57uCqx8QRSQQ==", "dev": true, "requires": { "callsites": "^3.0.0", @@ -2863,46 +3037,45 @@ } }, "@jest/test-result": { - "version": "25.2.4", - "resolved": "https://registry.npmjs.org/@jest/test-result/-/test-result-25.2.4.tgz", - "integrity": "sha512-AI7eUy+q2lVhFnaibDFg68NGkrxVWZdD6KBr9Hm6EvN0oAe7GxpEwEavgPfNHQjU2mi6g+NsFn/6QPgTUwM1qg==", + "version": "25.4.0", + "resolved": "https://registry.npmjs.org/@jest/test-result/-/test-result-25.4.0.tgz", + "integrity": "sha512-8BAKPaMCHlL941eyfqhWbmp3MebtzywlxzV+qtngQ3FH+RBqnoSAhNEPj4MG7d2NVUrMOVfrwuzGpVIK+QnMAA==", "dev": true, "requires": { - "@jest/console": "^25.2.3", - "@jest/transform": "^25.2.4", - "@jest/types": "^25.2.3", + "@jest/console": "^25.4.0", + "@jest/types": "^25.4.0", "@types/istanbul-lib-coverage": "^2.0.0", "collect-v8-coverage": "^1.0.0" } }, "@jest/test-sequencer": { - "version": "25.2.4", - "resolved": "https://registry.npmjs.org/@jest/test-sequencer/-/test-sequencer-25.2.4.tgz", - "integrity": "sha512-TEZm/Rkd6YgskdpTJdYLBtu6Gc11tfWPuSpatq0duH77ekjU8dpqX2zkPdY/ayuHxztV5LTJoV5BLtI9mZfXew==", + "version": "25.4.0", + "resolved": "https://registry.npmjs.org/@jest/test-sequencer/-/test-sequencer-25.4.0.tgz", + "integrity": "sha512-240cI+nsM3attx2bMp9uGjjHrwrpvxxrZi8Tyqp/cfOzl98oZXVakXBgxODGyBYAy/UGXPKXLvNc2GaqItrsJg==", "dev": true, "requires": { - "@jest/test-result": "^25.2.4", - "jest-haste-map": "^25.2.3", - "jest-runner": "^25.2.4", - "jest-runtime": "^25.2.4" + "@jest/test-result": "^25.4.0", + "jest-haste-map": "^25.4.0", + "jest-runner": "^25.4.0", + "jest-runtime": "^25.4.0" } }, "@jest/transform": { - "version": "25.2.4", - "resolved": "https://registry.npmjs.org/@jest/transform/-/transform-25.2.4.tgz", - "integrity": "sha512-6eRigvb+G6bs4kW5j1/y8wu4nCrmVuIe0epPBbiWaYlwawJ8yi1EIyK3d/btDqmBpN5GpN4YhR6iPPnDmkYdTA==", + "version": "25.4.0", + "resolved": "https://registry.npmjs.org/@jest/transform/-/transform-25.4.0.tgz", + "integrity": "sha512-t1w2S6V1sk++1HHsxboWxPEuSpN8pxEvNrZN+Ud/knkROWtf8LeUmz73A4ezE8476a5AM00IZr9a8FO9x1+j3g==", "dev": true, "requires": { "@babel/core": "^7.1.0", - "@jest/types": "^25.2.3", + "@jest/types": "^25.4.0", "babel-plugin-istanbul": "^6.0.0", "chalk": "^3.0.0", "convert-source-map": "^1.4.0", "fast-json-stable-stringify": "^2.0.0", "graceful-fs": "^4.2.3", - "jest-haste-map": "^25.2.3", - "jest-regex-util": "^25.2.1", - "jest-util": "^25.2.3", + "jest-haste-map": "^25.4.0", + "jest-regex-util": "^25.2.6", + "jest-util": "^25.4.0", "micromatch": "^4.0.2", "pirates": "^4.0.1", "realpath-native": "^2.0.0", @@ -2980,9 +3153,9 @@ } }, "@jest/types": { - "version": "25.2.3", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-25.2.3.tgz", - "integrity": "sha512-6oLQwO9mKif3Uph3RX5J1i3S7X7xtDHWBaaaoeKw8hOzV6YUd0qDcYcHZ6QXMHDIzSr7zzrEa51o2Ovlj6AtKQ==", + "version": "25.4.0", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-25.4.0.tgz", + "integrity": "sha512-XBeaWNzw2PPnGW5aXvZt3+VO60M+34RY3XDsCK5tW7kyj3RK0XClRutCfjqcBuaR2aBQTbluEDME9b5MB9UAPw==", "dev": true, "requires": { "@types/istanbul-lib-coverage": "^2.0.0", @@ -3087,6 +3260,15 @@ "integrity": "sha512-shAmDyaQC4H92APFoIaVDHCx5bStIocgvbwQyxPRrbUY20V1EYTbSDchWbuwlMG3V17cprZhA6+78JfB+3DTPw==", "dev": true }, + "@samverschueren/stream-to-observable": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/@samverschueren/stream-to-observable/-/stream-to-observable-0.3.0.tgz", + "integrity": "sha512-MI4Xx6LHs4Webyvi6EbspgyAb4D2Q2VtnCQ1blOJcoLS6mVa8lNN2rkIy1CVxfTUpoyIbCTkXES1rLXztFD1lg==", + "dev": true, + "requires": { + "any-observable": "^0.3.0" + } + }, "@schematics/angular": { "version": "8.3.26", "resolved": "https://registry.npmjs.org/@schematics/angular/-/angular-8.3.26.tgz", @@ -3125,9 +3307,9 @@ } }, "@sinonjs/commons": { - "version": "1.7.1", - "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-1.7.1.tgz", - "integrity": "sha512-Debi3Baff1Qu1Unc3mjJ96MgpbwTn43S1+9yJ0llWygPwDNu2aaWBD6yc9y/Z8XDRNhx7U+u2UDg2OGQXkclUQ==", + "version": "1.7.2", + "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-1.7.2.tgz", + "integrity": "sha512-+DUO6pnp3udV/v2VfUWgaY5BIE1IfT7lLfeDzPVeMT1XKkaAp9LgSI9x5RtrFQoZ9Oi0PgXQQHPaoKu7dCjVxw==", "dev": true, "requires": { "type-detect": "4.0.8" @@ -3142,9 +3324,9 @@ } }, "@types/babel__core": { - "version": "7.1.6", - "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.1.6.tgz", - "integrity": "sha512-tTnhWszAqvXnhW7m5jQU9PomXSiKXk2sFxpahXvI20SZKu9ylPi8WtIxueZ6ehDWikPT0jeFujMj3X4ZHuf3Tg==", + "version": "7.1.7", + "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.1.7.tgz", + "integrity": "sha512-RL62NqSFPCDK2FM1pSDH0scHpJvsXtZNiYlMB73DgPBaG1E38ZYVL+ei5EkWRbr+KC4YNiAUNBnRj+bgwpgjMw==", "dev": true, "requires": { "@babel/parser": "^7.1.0", @@ -3174,18 +3356,46 @@ } }, "@types/babel__traverse": { - "version": "7.0.9", - "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.0.9.tgz", - "integrity": "sha512-jEFQ8L1tuvPjOI8lnpaf73oCJe+aoxL6ygqSy6c8LcW98zaC+4mzWuQIRCEvKeCOu+lbqdXcg4Uqmm1S8AP1tw==", + "version": "7.0.10", + "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.0.10.tgz", + "integrity": "sha512-74fNdUGrWsgIB/V9kTO5FGHPWYY6Eqn+3Z7L6Hc4e/BxjYV7puvBqp5HwsVYYfLm6iURYBNCx4Ut37OF9yitCw==", "dev": true, "requires": { "@babel/types": "^7.3.0" } }, + "@types/blob-util": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/@types/blob-util/-/blob-util-1.3.3.tgz", + "integrity": "sha512-4ahcL/QDnpjWA2Qs16ZMQif7HjGP2cw3AGjHabybjw7Vm1EKu+cfQN1D78BaZbS1WJNa1opSMF5HNMztx7lR0w==", + "dev": true + }, + "@types/bluebird": { + "version": "3.5.29", + "resolved": "https://registry.npmjs.org/@types/bluebird/-/bluebird-3.5.29.tgz", + "integrity": "sha512-kmVtnxTuUuhCET669irqQmPAez4KFnFVKvpleVRyfC3g+SHD1hIkFZcWLim9BVcwUBLO59o8VZE4yGCmTif8Yw==", + "dev": true + }, + "@types/chai": { + "version": "4.2.7", + "resolved": "https://registry.npmjs.org/@types/chai/-/chai-4.2.7.tgz", + "integrity": "sha512-luq8meHGYwvky0O7u0eQZdA7B4Wd9owUCqvbw2m3XCrCU8mplYOujMBbvyS547AxJkC+pGnd0Cm15eNxEUNU8g==", + "dev": true + }, + "@types/chai-jquery": { + "version": "1.1.40", + "resolved": "https://registry.npmjs.org/@types/chai-jquery/-/chai-jquery-1.1.40.tgz", + "integrity": "sha512-mCNEZ3GKP7T7kftKeIs7QmfZZQM7hslGSpYzKbOlR2a2HCFf9ph4nlMRA9UnuOETeOQYJVhJQK7MwGqNZVyUtQ==", + "dev": true, + "requires": { + "@types/chai": "*", + "@types/jquery": "*" + } + }, "@types/chart.js": { - "version": "2.9.18", - "resolved": "https://registry.npmjs.org/@types/chart.js/-/chart.js-2.9.18.tgz", - "integrity": "sha512-D7oaYQqYGdfoa1Wv9doxQJ9Sv/W7jfbiXMT/wVRiM0AsPJsHWLRn7U46xhDkPRGmLCpQGlN2oZYBIwEpMMleog==", + "version": "2.9.19", + "resolved": "https://registry.npmjs.org/@types/chart.js/-/chart.js-2.9.19.tgz", + "integrity": "sha512-sFxlMb+ElfJelXh0Z8spmiLRrnXCd7CaT6WGQtckhjETK1H5i1nYKN4TOExhqPeDZ6u+w4bJ20UYqELWOEfAKQ==", "requires": { "moment": "^2.10.2" } @@ -3238,21 +3448,6 @@ "@types/istanbul-lib-report": "*" } }, - "@types/jasmine": { - "version": "3.5.10", - "resolved": "https://registry.npmjs.org/@types/jasmine/-/jasmine-3.5.10.tgz", - "integrity": "sha512-3F8qpwBAiVc5+HPJeXJpbrl+XjawGmciN5LgiO7Gv1pl1RHtjoMNqZpqEksaPJW05ViKe8snYInRs6xB25Xdew==", - "dev": true - }, - "@types/jasminewd2": { - "version": "2.0.8", - "resolved": "https://registry.npmjs.org/@types/jasminewd2/-/jasminewd2-2.0.8.tgz", - "integrity": "sha512-d9p31r7Nxk0ZH0U39PTH0hiDlJ+qNVGjlt1ucOoTUptxb2v+Y5VMnsxfwN+i3hK4yQnqBi3FMmoMFcd1JHDxdg==", - "dev": true, - "requires": { - "@types/jasmine": "*" - } - }, "@types/jest": { "version": "25.1.4", "resolved": "https://registry.npmjs.org/@types/jest/-/jest-25.1.4.tgz", @@ -3263,6 +3458,15 @@ "pretty-format": "^25.1.0" } }, + "@types/jquery": { + "version": "3.3.31", + "resolved": "https://registry.npmjs.org/@types/jquery/-/jquery-3.3.31.tgz", + "integrity": "sha512-Lz4BAJihoFw5nRzKvg4nawXPzutkv7wmfQ5121avptaSIXlDNJCUuxZxX/G+9EVidZGuO0UBlk+YjKbwRKJigg==", + "dev": true, + "requires": { + "@types/sizzle": "*" + } + }, "@types/lodash": { "version": "4.14.149", "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.14.149.tgz", @@ -3275,36 +3479,58 @@ "integrity": "sha512-tHq6qdbT9U1IRSGf14CL0pUlULksvY9OZ+5eEgl1N7t+OA3tGvNpxJCzuKQlsNgCVwbAs670L1vcVQi8j9HjnA==", "dev": true }, + "@types/mocha": { + "version": "5.2.7", + "resolved": "https://registry.npmjs.org/@types/mocha/-/mocha-5.2.7.tgz", + "integrity": "sha512-NYrtPht0wGzhwe9+/idPaBB+TqkY9AhTvOLMkThm0IoEfLaiVQZwBwyJ5puCkO3AUCWrmcoePjp2mbFocKy4SQ==", + "dev": true + }, "@types/node": { "version": "12.12.34", "resolved": "https://registry.npmjs.org/@types/node/-/node-12.12.34.tgz", "integrity": "sha512-BneGN0J9ke24lBRn44hVHNeDlrXRYF+VRp0HbSUNnEZahXGAysHZIqnf/hER6aabdBgzM4YOV4jrR8gj4Zfi0g==", "dev": true }, + "@types/normalize-package-data": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/@types/normalize-package-data/-/normalize-package-data-2.4.0.tgz", + "integrity": "sha512-f5j5b/Gf71L+dbqxIpQ4Z2WlmI/mPJ0fOkGGmFgtb6sAu97EPczzbS3/tJKxmcYDj55OX6ssqwDAWOHIYDRDGA==", + "dev": true + }, "@types/prettier": { "version": "1.19.1", "resolved": "https://registry.npmjs.org/@types/prettier/-/prettier-1.19.1.tgz", "integrity": "sha512-5qOlnZscTn4xxM5MeGXAMOsIOIKIbh9e85zJWfBRVPlRMEVawzoPhINYbRGkBZCI8LxvBe7tJCdWiarA99OZfQ==", "dev": true }, - "@types/q": { - "version": "0.0.32", - "resolved": "https://registry.npmjs.org/@types/q/-/q-0.0.32.tgz", - "integrity": "sha1-vShOV8hPEyXacCur/IKlMoGQwMU=", - "dev": true - }, - "@types/selenium-webdriver": { - "version": "3.0.17", - "resolved": "https://registry.npmjs.org/@types/selenium-webdriver/-/selenium-webdriver-3.0.17.tgz", - "integrity": "sha512-tGomyEuzSC1H28y2zlW6XPCaDaXFaD6soTdb4GNdmte2qfHtrKqhy0ZFs4r/1hpazCfEZqeTSRLvSasmEx89uw==", - "dev": true - }, "@types/simplebar": { "version": "5.1.1", "resolved": "https://registry.npmjs.org/@types/simplebar/-/simplebar-5.1.1.tgz", "integrity": "sha512-nC9iBQ4dfvvzJ3iAbL1qCfwjUyaF8EO56l/ApcRXUFK2zLOb8GDXC55V08JZvpzkUxGHtWVunp17KKH/3/KFJA==", "dev": true }, + "@types/sinon": { + "version": "7.5.1", + "resolved": "https://registry.npmjs.org/@types/sinon/-/sinon-7.5.1.tgz", + "integrity": "sha512-EZQUP3hSZQyTQRfiLqelC9NMWd1kqLcmQE0dMiklxBkgi84T+cHOhnKpgk4NnOWpGX863yE6+IaGnOXUNFqDnQ==", + "dev": true + }, + "@types/sinon-chai": { + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/@types/sinon-chai/-/sinon-chai-3.2.3.tgz", + "integrity": "sha512-TOUFS6vqS0PVL1I8NGVSNcFaNJtFoyZPXZ5zur+qlhDfOmQECZZM4H4kKgca6O8L+QceX/ymODZASfUfn+y4yQ==", + "dev": true, + "requires": { + "@types/chai": "*", + "@types/sinon": "*" + } + }, + "@types/sizzle": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/@types/sizzle/-/sizzle-2.3.2.tgz", + "integrity": "sha512-7EJYyKTL7tFR8+gDbB6Wwz/arpGa0Mywk1TJbNzKzHtzbwVmY4HR9WqS5VV7dsBUKQmPNr192jHr/VpBluj/hg==", + "dev": true + }, "@types/source-list-map": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/@types/source-list-map/-/source-list-map-0.1.2.tgz", @@ -3620,12 +3846,6 @@ "integrity": "sha512-wdlPY2tm/9XBr7QkKlq0WQVgiuGTX6YWPyRyBviSoScBuLfTVQhvwg6wJ369GJ/1nPfTLMfnrFIfjqVg6d+jQQ==", "dev": true }, - "adm-zip": { - "version": "0.4.14", - "resolved": "https://registry.npmjs.org/adm-zip/-/adm-zip-0.4.14.tgz", - "integrity": "sha512-/9aQCnQHF+0IiCl0qhXoK7qs//SwYE7zX8lsr/DNk1BRAHYxeLZPL4pguwK29gUEqasYQjqPtEpDRSWEkdHn9g==", - "dev": true - }, "agent-base": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-4.3.0.tgz", @@ -3733,6 +3953,12 @@ "integrity": "sha1-qCJQ3bABXponyoLoLqYDu/pF768=", "dev": true }, + "any-observable": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/any-observable/-/any-observable-0.3.0.tgz", + "integrity": "sha512-/FQM1EDkTsf63Ub2C6O7GuYFDsSXUwsaZDurV0np41ocwq0jthUAYCmhBX9f+KwlaCgIuWyr/4WlUQUBfKfZog==", + "dev": true + }, "anymatch": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.1.tgz", @@ -3770,6 +3996,12 @@ "integrity": "sha512-Y9J6ZjXtoYh8RnXVCMOU/ttDmk1aBjunq9vO0ta5x85WDQiQfUF9sIPBITdbiiIVcBo03Hi3jMxigBtsddlXRw==", "dev": true }, + "arch": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/arch/-/arch-2.1.1.tgz", + "integrity": "sha512-BLM56aPo9vLLFVa8+/+pJLnrZ7QGGTVHWsCwieAWT9o9K8UeGaQbzZbGoabWLOo2ksBCztoXdqBZBplqLDDCSg==", + "dev": true + }, "arg": { "version": "4.1.3", "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz", @@ -4087,16 +4319,16 @@ } }, "babel-jest": { - "version": "25.2.4", - "resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-25.2.4.tgz", - "integrity": "sha512-+yDzlyJVWrqih9i2Cvjpt7COaN8vUwCsKGtxJLzg6I0xhxD54K8mvDUCliPKLufyzHh/c5C4MRj4Vk7VMjOjIg==", + "version": "25.4.0", + "resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-25.4.0.tgz", + "integrity": "sha512-p+epx4K0ypmHuCnd8BapfyOwWwosNCYhedetQey1awddtfmEX0MmdxctGl956uwUmjwXR5VSS5xJcGX9DvdIog==", "dev": true, "requires": { - "@jest/transform": "^25.2.4", - "@jest/types": "^25.2.3", - "@types/babel__core": "^7.1.0", + "@jest/transform": "^25.4.0", + "@jest/types": "^25.4.0", + "@types/babel__core": "^7.1.7", "babel-plugin-istanbul": "^6.0.0", - "babel-preset-jest": "^25.2.1", + "babel-preset-jest": "^25.4.0", "chalk": "^3.0.0", "slash": "^3.0.0" }, @@ -4154,9 +4386,9 @@ } }, "babel-plugin-dynamic-import-node": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/babel-plugin-dynamic-import-node/-/babel-plugin-dynamic-import-node-2.3.0.tgz", - "integrity": "sha512-o6qFkpeQEBxcqt0XYlWzAVxNCSCZdUgcR8IRlhD/8DylxjjO4foPcvTW0GGKa/cVt3rvxZ7o5ippJ+/0nvLhlQ==", + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/babel-plugin-dynamic-import-node/-/babel-plugin-dynamic-import-node-2.3.3.tgz", + "integrity": "sha512-jZVI+s9Zg3IqA/kdi0i6UDCybUI3aSBLnglhYbSSjKlV7yF1F/5LWv8MakQmvYpnbJDS6fcBL2KzHSxNCMtWSQ==", "dev": true, "requires": { "object.assign": "^4.1.0" @@ -4176,23 +4408,40 @@ } }, "babel-plugin-jest-hoist": { - "version": "25.2.1", - "resolved": "https://registry.npmjs.org/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-25.2.1.tgz", - "integrity": "sha512-HysbCQfJhxLlyxDbKcB2ucGYV0LjqK4h6dBoI3RtFuOxTiTWK6XGZMsHb0tGh8iJdV4hC6Z2GCHzVvDeh9i0lQ==", + "version": "25.4.0", + "resolved": "https://registry.npmjs.org/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-25.4.0.tgz", + "integrity": "sha512-M3a10JCtTyKevb0MjuH6tU+cP/NVQZ82QPADqI1RQYY1OphztsCeIeQmTsHmF/NS6m0E51Zl4QNsI3odXSQF5w==", "dev": true, "requires": { "@types/babel__traverse": "^7.0.6" } }, + "babel-preset-current-node-syntax": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/babel-preset-current-node-syntax/-/babel-preset-current-node-syntax-0.1.2.tgz", + "integrity": "sha512-u/8cS+dEiK1SFILbOC8/rUI3ml9lboKuuMvZ/4aQnQmhecQAgPw5ew066C1ObnEAUmlx7dv/s2z52psWEtLNiw==", + "dev": true, + "requires": { + "@babel/plugin-syntax-async-generators": "^7.8.4", + "@babel/plugin-syntax-bigint": "^7.8.3", + "@babel/plugin-syntax-class-properties": "^7.8.3", + "@babel/plugin-syntax-json-strings": "^7.8.3", + "@babel/plugin-syntax-logical-assignment-operators": "^7.8.3", + "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3", + "@babel/plugin-syntax-numeric-separator": "^7.8.3", + "@babel/plugin-syntax-object-rest-spread": "^7.8.3", + "@babel/plugin-syntax-optional-catch-binding": "^7.8.3", + "@babel/plugin-syntax-optional-chaining": "^7.8.3" + } + }, "babel-preset-jest": { - "version": "25.2.1", - "resolved": "https://registry.npmjs.org/babel-preset-jest/-/babel-preset-jest-25.2.1.tgz", - "integrity": "sha512-zXHJBM5iR8oEO4cvdF83AQqqJf3tJrXy3x8nfu2Nlqvn4cneg4Ca8M7cQvC5S9BzDDy1O0tZ9iXru9J6E3ym+A==", + "version": "25.4.0", + "resolved": "https://registry.npmjs.org/babel-preset-jest/-/babel-preset-jest-25.4.0.tgz", + "integrity": "sha512-PwFiEWflHdu3JCeTr0Pb9NcHHE34qWFnPQRVPvqQITx4CsDCzs6o05923I10XvLvn9nNsRHuiVgB72wG/90ZHQ==", "dev": true, "requires": { - "@babel/plugin-syntax-bigint": "^7.0.0", - "@babel/plugin-syntax-object-rest-spread": "^7.0.0", - "babel-plugin-jest-hoist": "^25.2.1" + "babel-plugin-jest-hoist": "^25.4.0", + "babel-preset-current-node-syntax": "^0.1.2" } }, "babel-runtime": { @@ -4327,20 +4576,6 @@ "integrity": "sha512-Phlt0plgpIIBOGTT/ehfFnbNlfsDEiqmzE2KRXoX1bLIlir4X/MR+zSyBEkL05ffWgnRSf/DXv+WrUAVr93/ow==", "dev": true }, - "blocking-proxy": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/blocking-proxy/-/blocking-proxy-1.0.1.tgz", - "integrity": "sha512-KE8NFMZr3mN2E0HcvCgRtX7DjhiIQrwle+nSVJVC/yqFb9+xznHl2ZcoBp2L9qzkI4t4cBFJ1efXF8Dwi132RA==", - "dev": true, - "requires": { - "minimist": "1.2.5" - }, - "dependencies": { - "minimist": { - "version": "1.2.5" - } - } - }, "bluebird": { "version": "3.7.2", "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.7.2.tgz", @@ -4587,15 +4822,6 @@ "pkg-up": "^3.1.0" } }, - "browserstack": { - "version": "1.5.3", - "resolved": "https://registry.npmjs.org/browserstack/-/browserstack-1.5.3.tgz", - "integrity": "sha512-AO+mECXsW4QcqC9bxwM29O7qWa7bJT94uBFzeb5brylIQwawuEziwq20dPYbins95GlWzOawgyDNdjYAo32EKg==", - "dev": true, - "requires": { - "https-proxy-agent": "^2.2.1" - } - }, "bs-logger": { "version": "0.2.6", "resolved": "https://registry.npmjs.org/bs-logger/-/bs-logger-0.2.6.tgz", @@ -4625,6 +4851,12 @@ "isarray": "^1.0.0" } }, + "buffer-crc32": { + "version": "0.2.13", + "resolved": "https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-0.2.13.tgz", + "integrity": "sha1-DTM+PwDqxQqhRUq9MO+MKl2ackI=", + "dev": true + }, "buffer-equal": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/buffer-equal/-/buffer-equal-0.0.1.tgz", @@ -4759,6 +4991,12 @@ "unset-value": "^1.0.0" } }, + "cachedir": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/cachedir/-/cachedir-2.3.0.tgz", + "integrity": "sha512-A+Fezp4zxnit6FanDmv9EqXNAi3vt9DWp51/71UEhXukb7QUuvtv9344h91dyAxuTLoSYJFU299qzR3tzwPAhw==", + "dev": true + }, "call-me-maybe": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/call-me-maybe/-/call-me-maybe-1.0.1.tgz", @@ -4875,6 +5113,12 @@ "color-name": "^1.0.0" } }, + "check-more-types": { + "version": "2.24.0", + "resolved": "https://registry.npmjs.org/check-more-types/-/check-more-types-2.24.0.tgz", + "integrity": "sha1-FCD/sQ/URNz8ebQ4kbv//TKoRgA=", + "dev": true + }, "cheerio": { "version": "1.0.0-rc.3", "resolved": "https://registry.npmjs.org/cheerio/-/cheerio-1.0.0-rc.3.tgz", @@ -4901,9 +5145,9 @@ } }, "chokidar": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.3.1.tgz", - "integrity": "sha512-4QYCEWOcK3OJrxwvyyAOxFuhpvOVCYkr33LPfFNBjAD/w3sEzWsp2BUOkI4l9bHvWioAd0rc6NlHUOEaWkTeqg==", + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.4.0.tgz", + "integrity": "sha512-aXAaho2VJtisB/1fg1+3nlLJqGOuewTzQpd/Tz0yTg2R0e4IGtshYvtjowyEumcBv2z+y4+kc75Mz7j5xJskcQ==", "dev": true, "requires": { "anymatch": "~3.1.1", @@ -4913,7 +5157,7 @@ "is-binary-path": "~2.1.0", "is-glob": "~4.0.1", "normalize-path": "~3.0.0", - "readdirp": "~3.3.0" + "readdirp": "~3.4.0" }, "dependencies": { "fsevents": { @@ -4967,12 +5211,6 @@ "integrity": "sha512-7p4Kn/gffhQaavNfyDFg7LS5S/UT1JAjyGd4UqR2+jzoYF02eDkj0Ec3+48TsIa4zghjLY87nQHIh/ecK9qLdw==", "dev": true }, - "circular-json": { - "version": "0.5.9", - "resolved": "https://registry.npmjs.org/circular-json/-/circular-json-0.5.9.tgz", - "integrity": "sha512-4ivwqHpIFJZBuhN3g/pEcdbnGUywkBblloGbkglyloVjjR3uT6tieI89MVOfbP2tHX5sgb01FuLgAOzebNlJNQ==", - "dev": true - }, "cjson": { "version": "0.5.0", "resolved": "https://registry.npmjs.org/cjson/-/cjson-0.5.0.tgz", @@ -5031,10 +5269,44 @@ "restore-cursor": "^3.1.0" } }, + "cli-table3": { + "version": "0.5.1", + "resolved": "https://registry.npmjs.org/cli-table3/-/cli-table3-0.5.1.tgz", + "integrity": "sha512-7Qg2Jrep1S/+Q3EceiZtQcDPWxhAvBw+ERf1162v4sikJrvojMHFqXt8QIVha8UlH9rgU0BeWPytZ9/TzYqlUw==", + "dev": true, + "requires": { + "colors": "^1.1.2", + "object-assign": "^4.1.0", + "string-width": "^2.1.1" + } + }, + "cli-truncate": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/cli-truncate/-/cli-truncate-0.2.1.tgz", + "integrity": "sha1-nxXPuwcFAFNpIWxiasfQWrkN1XQ=", + "dev": true, + "requires": { + "slice-ansi": "0.0.4", + "string-width": "^1.0.1" + }, + "dependencies": { + "string-width": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz", + "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=", + "dev": true, + "requires": { + "code-point-at": "^1.0.0", + "is-fullwidth-code-point": "^1.0.0", + "strip-ansi": "^3.0.0" + } + } + } + }, "cli-width": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/cli-width/-/cli-width-2.2.0.tgz", - "integrity": "sha1-/xnt6Kml5XkyQUewwR8PvLq+1jk=", + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/cli-width/-/cli-width-2.2.1.tgz", + "integrity": "sha512-GRMWDxpOB6Dgk2E5Uo+3eEBvtOOlimMmpbFiKuLFnQzYDavtLFY3K5ona41jgN/WdRZtG7utuVSVTL4HbZHGkw==", "dev": true }, "cliui": { @@ -5125,9 +5397,9 @@ } }, "collect-v8-coverage": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/collect-v8-coverage/-/collect-v8-coverage-1.0.0.tgz", - "integrity": "sha512-VKIhJgvk8E1W28m5avZ2Gv2Ruv5YiF56ug2oclvaG9md69BuZImMG2sk9g7QNKLUbtYAKQjXjYxbYZVUlMMKmQ==", + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/collect-v8-coverage/-/collect-v8-coverage-1.0.1.tgz", + "integrity": "sha512-iBPtljfCNcTKNAto0KEtDfZ3qzjJvqE3aTGZsbhjSBlorqpXJlaWWtPO35D+ZImoC3KWejX64o+yPGxhWSTzfg==", "dev": true }, "collection-visit": { @@ -5187,6 +5459,12 @@ "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", "dev": true }, + "common-tags": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/common-tags/-/common-tags-1.8.0.tgz", + "integrity": "sha512-6P6g0uetGpW/sdyUy/iQQCbFF0kWVMSIVSyYz7Zgjcgh8mgw8PQzDNZeyZ5DQ2gM7LBoZPHmnjz8rUthkBG5tw==", + "dev": true + }, "commondir": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/commondir/-/commondir-1.0.1.tgz", @@ -5438,9 +5716,9 @@ } }, "p-limit": { - "version": "2.2.2", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.2.2.tgz", - "integrity": "sha512-WGR+xHecKTr7EbUEhyLSh5Dube9JtdiG78ufaeLxTgpudf/20KqyMioIUZJAezlTIi6evxuoUs9YXc11cU+yzQ==", + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", "dev": true, "requires": { "p-try": "^2.0.0" @@ -5467,17 +5745,17 @@ } }, "core-js": { - "version": "3.6.4", - "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.6.4.tgz", - "integrity": "sha512-4paDGScNgZP2IXXilaffL9X7968RuvwlkK3xWtZRVqgd8SYNiVKRJvkFd1aqqEuPfN7E68ZHEp9hDj6lHj4Hyw==" + "version": "3.6.5", + "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.6.5.tgz", + "integrity": "sha512-vZVEEwZoIsI+vPEuoF9Iqf5H7/M3eeQqWlQnYa8FSKKePuYTf5MWnxb5SDAzCa60b3JBRS5g9b+Dq7b1y/RCrA==" }, "core-js-compat": { - "version": "3.6.4", - "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.6.4.tgz", - "integrity": "sha512-zAa3IZPvsJ0slViBQ2z+vgyyTuhd3MFn1rBQjZSKVEgB0UMYhUkCj9jJUVPgGTGqWvsBVmfnruXgTcNyTlEiSA==", + "version": "3.6.5", + "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.6.5.tgz", + "integrity": "sha512-7ItTKOhOZbznhXAQ2g/slGg1PJV5zDO/WdkTwi7UEOJmkvsE32PWvx6mKtDjiMpjnR2CNf6BAD6sSxIlv7ptng==", "dev": true, "requires": { - "browserslist": "^4.8.3", + "browserslist": "^4.8.5", "semver": "7.0.0" }, "dependencies": { @@ -5531,9 +5809,9 @@ }, "dependencies": { "ajv": { - "version": "6.12.0", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.0.tgz", - "integrity": "sha512-D6gFiFA0RRLyUbvijN74DWAjXSFxWKaWP7mldxkVhyhAV3+SWA9HEJPHQ2c9soIeTFJqcSdFDGFgdqs1iUU2Hw==", + "version": "6.12.2", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.2.tgz", + "integrity": "sha512-k+V+hzjm5q/Mr8ef/1Y9goCmlsK4I6Sm74teeyGvFk1XrOsbsKLjEdrvny42CZ+a8sXbk8KWpY/bDwS+FLL2UQ==", "dev": true, "requires": { "fast-deep-equal": "^3.1.1", @@ -5549,9 +5827,9 @@ "dev": true }, "schema-utils": { - "version": "2.6.5", - "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-2.6.5.tgz", - "integrity": "sha512-5KXuwKziQrTVHh8j/Uxz+QUbxkaLW9X/86NBlx/gnKgtsZA2GIVMUn17qWhRFwF8jdYb3Dig5hRO/W5mZqy6SQ==", + "version": "2.6.6", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-2.6.6.tgz", + "integrity": "sha512-wHutF/WPSbIi9x6ctjGGk2Hvl0VOz5l3EKEuKbjPlB30mKZUzb9A5k9yEXRX3pwyqVLPvpfZZEllaFq/M718hA==", "dev": true, "requires": { "ajv": "^6.12.0", @@ -5717,6 +5995,135 @@ "integrity": "sha1-WW6WmP0MgOEgOMK4LW6xs1tiJNk=", "dev": true }, + "cypress": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/cypress/-/cypress-4.4.0.tgz", + "integrity": "sha512-ZpsV3pVemANGi4Cxu0UIqFv23uHdDJZYlKY+8P/eixujCpI1TQ5RSPBp2grfV3ZvlGYrOXPJY44j9iEh1xoQug==", + "dev": true, + "requires": { + "@cypress/listr-verbose-renderer": "0.4.1", + "@cypress/request": "2.88.5", + "@cypress/xvfb": "1.2.4", + "@types/blob-util": "1.3.3", + "@types/bluebird": "3.5.29", + "@types/chai": "4.2.7", + "@types/chai-jquery": "1.1.40", + "@types/jquery": "3.3.31", + "@types/lodash": "4.14.149", + "@types/minimatch": "3.0.3", + "@types/mocha": "5.2.7", + "@types/sinon": "7.5.1", + "@types/sinon-chai": "3.2.3", + "@types/sizzle": "2.3.2", + "arch": "2.1.1", + "bluebird": "3.7.2", + "cachedir": "2.3.0", + "chalk": "2.4.2", + "check-more-types": "2.24.0", + "cli-table3": "0.5.1", + "commander": "4.1.0", + "common-tags": "1.8.0", + "debug": "4.1.1", + "eventemitter2": "4.1.2", + "execa": "1.0.0", + "executable": "4.1.1", + "extract-zip": "1.7.0", + "fs-extra": "8.1.0", + "getos": "3.1.4", + "is-ci": "2.0.0", + "is-installed-globally": "0.1.0", + "lazy-ass": "1.6.0", + "listr": "0.14.3", + "lodash": "4.17.15", + "log-symbols": "3.0.0", + "minimist": "1.2.5", + "moment": "2.24.0", + "ospath": "1.2.2", + "pretty-bytes": "5.3.0", + "ramda": "0.26.1", + "request-progress": "3.0.0", + "supports-color": "7.1.0", + "tmp": "0.1.0", + "untildify": "4.0.0", + "url": "0.11.0", + "yauzl": "2.10.0" + }, + "dependencies": { + "commander": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-4.1.0.tgz", + "integrity": "sha512-NIQrwvv9V39FHgGFm36+U9SMQzbiHvU79k+iADraJTpmrFFfx7Ds0IvDoAdZsDrknlkRk14OYoWXb57uTh7/sw==", + "dev": true + }, + "cross-spawn": { + "version": "6.0.5", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.5.tgz", + "integrity": "sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ==", + "dev": true, + "requires": { + "nice-try": "^1.0.4", + "path-key": "^2.0.1", + "semver": "^5.5.0", + "shebang-command": "^1.2.0", + "which": "^1.2.9" + } + }, + "execa": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/execa/-/execa-1.0.0.tgz", + "integrity": "sha512-adbxcyWV46qiHyvSp50TKt05tB4tK3HcmF7/nxfAdhnox83seTDbwnaqKO4sXRy7roHAIFqJP/Rw/AuEbX61LA==", + "dev": true, + "requires": { + "cross-spawn": "^6.0.0", + "get-stream": "^4.0.0", + "is-stream": "^1.1.0", + "npm-run-path": "^2.0.0", + "p-finally": "^1.0.0", + "signal-exit": "^3.0.0", + "strip-eof": "^1.0.0" + } + }, + "get-stream": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-4.1.0.tgz", + "integrity": "sha512-GMat4EJ5161kIy2HevLlr4luNjBgvmj413KaQA7jt4V8B4RDsfpHk7WQ9GVqfYyyx8OS/L66Kox+rJRNklLK7w==", + "dev": true, + "requires": { + "pump": "^3.0.0" + } + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true + }, + "semver": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", + "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", + "dev": true + }, + "supports-color": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.1.0.tgz", + "integrity": "sha512-oRSIpR8pxT1Wr2FquTNnGet79b3BWljqOuoW/h4oBhxJ/HUbX5nX6JSruTkvXDCFMwDPvsaTTbvMLKZWSy0R5g==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + }, + "tmp": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.1.0.tgz", + "integrity": "sha512-J7Z2K08jbGcdA1kkQpJSqLF6T0tdQqpR2pnSUXsIchbPdTI9v3e85cLW0d6WDhwuAleOV71j2xWs8qMPfK7nKw==", + "dev": true, + "requires": { + "rimraf": "^2.6.3" + } + } + } + }, "d": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/d/-/d-1.0.1.tgz", @@ -5759,6 +6166,12 @@ "whatwg-url": "^7.0.0" } }, + "date-fns": { + "version": "1.30.1", + "resolved": "https://registry.npmjs.org/date-fns/-/date-fns-1.30.1.tgz", + "integrity": "sha512-hBSVCvSmWC+QypYObzwGOd9wqdDpOt+0wl0KbU+R+uuZBS1jN8VsD1ss3irQDknRj5NvxiTF6oj/nDRnN/UQNw==", + "dev": true + }, "debug": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", @@ -6043,9 +6456,9 @@ "dev": true }, "diff-sequences": { - "version": "25.2.1", - "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-25.2.1.tgz", - "integrity": "sha512-foe7dXnGlSh3jR1ovJmdv+77VQj98eKCHHwJPbZ2eEf0fHwKbkZicpPxEch9smZ+n2dnF6QFwkOQdLq9hpeJUg==", + "version": "25.2.6", + "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-25.2.6.tgz", + "integrity": "sha512-Hq8o7+6GaZeoFjtpgvRBUknSXNeJiCx7V9Fr94ZMljNiCr9n9L8H8aJqgWOQiDDGdyn29fRNcDdRVJ5fdyihfg==", "dev": true }, "diffie-hellman": { @@ -6193,9 +6606,15 @@ "dev": true }, "electron-to-chromium": { - "version": "1.3.391", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.391.tgz", - "integrity": "sha512-WOi6loSnDmfICOqGRrgeK7bZeWDAbGjCptDhI5eyJAqSzWfoeRuOOU1rOTZRL29/9AaxTndZB6Uh8YrxRfZJqw==", + "version": "1.3.418", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.418.tgz", + "integrity": "sha512-i2QrQtHes5fK/F9QGG5XacM5WKEuR322fxTYF9e8O9Gu0mc0WmjjwGpV8c7Htso6Zf2Di18lc3SIPxmMeRFBug==", + "dev": true + }, + "elegant-spinner": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/elegant-spinner/-/elegant-spinner-1.0.1.tgz", + "integrity": "sha1-2wQ1IcldfjA/2PNFvtwzSc+wcp4=", "dev": true }, "elliptic": { @@ -6419,24 +6838,18 @@ "dev": true }, "escodegen": { - "version": "1.9.1", - "resolved": "https://registry.npmjs.org/escodegen/-/escodegen-1.9.1.tgz", - "integrity": "sha512-6hTjO1NAWkHnDk3OqQ4YrCuwwmGHL9S3nPlzBOUG/R44rda3wLNrfvQ5fkSGjyhHFKM7ALPKcKGrwvCLe0lC7Q==", + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/escodegen/-/escodegen-1.14.1.tgz", + "integrity": "sha512-Bmt7NcRySdIfNPfU2ZoXDrrXsG9ZjvDxcAlMfDUgRBjLOWTuIACXPBFJH7Z+cLb40JeQco5toikyc9t9P8E9SQ==", "dev": true, "requires": { - "esprima": "^3.1.3", + "esprima": "^4.0.1", "estraverse": "^4.2.0", "esutils": "^2.0.2", "optionator": "^0.8.1", "source-map": "~0.6.1" }, "dependencies": { - "esprima": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/esprima/-/esprima-3.1.3.tgz", - "integrity": "sha1-/cpRzuYTOJXjyI1TXOSdv/YqRjM=", - "dev": true - }, "source-map": { "version": "0.6.1", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", @@ -6520,6 +6933,12 @@ "through": "~2.3.1" } }, + "eventemitter2": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/eventemitter2/-/eventemitter2-4.1.2.tgz", + "integrity": "sha1-DhqEd6+CGm7zmVsxG/dMI6UkfxU=", + "dev": true + }, "eventemitter3": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-4.0.0.tgz", @@ -6571,12 +6990,35 @@ "strip-eof": "^1.0.0" } }, + "executable": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/executable/-/executable-4.1.1.tgz", + "integrity": "sha512-8iA79xD3uAch729dUG8xaaBBFGaEa0wdD2VkYLFHwlqosEj/jT66AzcreRDSgV7ehnNLBW2WR5jIXwGKjVdTLg==", + "dev": true, + "requires": { + "pify": "^2.2.0" + }, + "dependencies": { + "pify": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", + "integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=", + "dev": true + } + } + }, "exit": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/exit/-/exit-0.1.2.tgz", "integrity": "sha1-BjJjj42HfMghB9MKD/8aF8uhzQw=", "dev": true }, + "exit-hook": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/exit-hook/-/exit-hook-1.1.1.tgz", + "integrity": "sha1-8FyiM7SMBdVP/wd2XfhQfpXAL/g=", + "dev": true + }, "expand-brackets": { "version": "2.1.4", "resolved": "https://registry.npmjs.org/expand-brackets/-/expand-brackets-2.1.4.tgz", @@ -6637,17 +7079,17 @@ } }, "expect": { - "version": "25.2.4", - "resolved": "https://registry.npmjs.org/expect/-/expect-25.2.4.tgz", - "integrity": "sha512-hfuPhPds4yOsZtIw4kwAg70r0hqGmpqekgA+VX7pf/3wZ6FY+xIOXZhNsPMMMsspYG/YIsbAiwqsdnD4Ht+bCA==", + "version": "25.4.0", + "resolved": "https://registry.npmjs.org/expect/-/expect-25.4.0.tgz", + "integrity": "sha512-7BDIX99BTi12/sNGJXA9KMRcby4iAmu1xccBOhyKCyEhjcVKS3hPmHdA/4nSI9QGIOkUropKqr3vv7WMDM5lvQ==", "dev": true, "requires": { - "@jest/types": "^25.2.3", + "@jest/types": "^25.4.0", "ansi-styles": "^4.0.0", - "jest-get-type": "^25.2.1", - "jest-matcher-utils": "^25.2.3", - "jest-message-util": "^25.2.4", - "jest-regex-util": "^25.2.1" + "jest-get-type": "^25.2.6", + "jest-matcher-utils": "^25.4.0", + "jest-message-util": "^25.4.0", + "jest-regex-util": "^25.2.6" }, "dependencies": { "ansi-styles": { @@ -6858,38 +7300,41 @@ } } }, - "extsprintf": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.3.0.tgz", - "integrity": "sha1-lpGEQOMEGnpBT4xS48V06zw+HgU=", - "dev": true - }, - "falafel": { - "version": "2.2.4", - "resolved": "https://registry.npmjs.org/falafel/-/falafel-2.2.4.tgz", - "integrity": "sha512-0HXjo8XASWRmsS0X1EkhwEMZaD3Qvp7FfURwjLKjG1ghfRm/MGZl2r4cWUTv41KdNghTw4OUMmVtdGQp3+H+uQ==", + "extract-zip": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/extract-zip/-/extract-zip-1.7.0.tgz", + "integrity": "sha512-xoh5G1W/PB0/27lXgMQyIhP5DSY/LhoCsOyZgb+6iMmRtCwVBo55uKaMoEYrDCKQhWvqEip5ZPKAc6eFNyf/MA==", "dev": true, "requires": { - "acorn": "^7.1.1", - "foreach": "^2.0.5", - "isarray": "^2.0.1", - "object-keys": "^1.0.6" + "concat-stream": "^1.6.2", + "debug": "^2.6.9", + "mkdirp": "^0.5.4", + "yauzl": "^2.10.0" }, "dependencies": { - "acorn": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-7.1.1.tgz", - "integrity": "sha512-add7dgA5ppRPxCFJoAGfMDi7PIBXq1RtGo7BhbLaxwrXPOmw8gq48Y9ozT01hUKy9byMjlR20EJhu5zlkErEkg==", - "dev": true + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "requires": { + "ms": "2.0.0" + } }, - "isarray": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.5.tgz", - "integrity": "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==", + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", "dev": true } } }, + "extsprintf": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.3.0.tgz", + "integrity": "sha1-lpGEQOMEGnpBT4xS48V06zw+HgU=", + "dev": true + }, "fancy-log": { "version": "1.3.3", "resolved": "https://registry.npmjs.org/fancy-log/-/fancy-log-1.3.3.tgz", @@ -6958,6 +7403,15 @@ "bser": "2.1.1" } }, + "fd-slicer": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/fd-slicer/-/fd-slicer-1.1.0.tgz", + "integrity": "sha1-JcfInLH5B3+IkbvmHY85Dq4lbx4=", + "dev": true, + "requires": { + "pend": "~1.2.0" + } + }, "figgy-pudding": { "version": "3.5.2", "resolved": "https://registry.npmjs.org/figgy-pudding/-/figgy-pudding-3.5.2.tgz", @@ -6984,9 +7438,9 @@ }, "dependencies": { "ajv": { - "version": "6.12.0", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.0.tgz", - "integrity": "sha512-D6gFiFA0RRLyUbvijN74DWAjXSFxWKaWP7mldxkVhyhAV3+SWA9HEJPHQ2c9soIeTFJqcSdFDGFgdqs1iUU2Hw==", + "version": "6.12.2", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.2.tgz", + "integrity": "sha512-k+V+hzjm5q/Mr8ef/1Y9goCmlsK4I6Sm74teeyGvFk1XrOsbsKLjEdrvny42CZ+a8sXbk8KWpY/bDwS+FLL2UQ==", "dev": true, "requires": { "fast-deep-equal": "^3.1.1", @@ -7002,9 +7456,9 @@ "dev": true }, "schema-utils": { - "version": "2.6.5", - "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-2.6.5.tgz", - "integrity": "sha512-5KXuwKziQrTVHh8j/Uxz+QUbxkaLW9X/86NBlx/gnKgtsZA2GIVMUn17qWhRFwF8jdYb3Dig5hRO/W5mZqy6SQ==", + "version": "2.6.6", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-2.6.6.tgz", + "integrity": "sha512-wHutF/WPSbIi9x6ctjGGk2Hvl0VOz5l3EKEuKbjPlB30mKZUzb9A5k9yEXRX3pwyqVLPvpfZZEllaFq/M718hA==", "dev": true, "requires": { "ajv": "^6.12.0", @@ -7085,18 +7539,18 @@ } }, "make-dir": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.0.2.tgz", - "integrity": "sha512-rYKABKutXa6vXTXhoV18cBE7PaewPXHe/Bdq4v+ZLMhxbWApkFFplT0LcbMW+6BbjnQXzZ/sAvSE/JdguApG5w==", + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz", + "integrity": "sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==", "dev": true, "requires": { "semver": "^6.0.0" } }, "p-limit": { - "version": "2.2.2", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.2.2.tgz", - "integrity": "sha512-WGR+xHecKTr7EbUEhyLSh5Dube9JtdiG78ufaeLxTgpudf/20KqyMioIUZJAezlTIi6evxuoUs9YXc11cU+yzQ==", + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", "dev": true, "requires": { "p-try": "^2.0.0" @@ -7221,100 +7675,36 @@ } }, "fontkit": { - "version": "1.8.0", - "resolved": "https://registry.npmjs.org/fontkit/-/fontkit-1.8.0.tgz", - "integrity": "sha512-EFDRCca7khfQWYu1iFhsqeABpi87f03MBdkT93ZE6YhqCdMzb5Eojb6c4dlJikGv5liuhByyzA7ikpIPTSBWbQ==", + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/fontkit/-/fontkit-1.8.1.tgz", + "integrity": "sha512-BsNCjDoYRxmNWFdAuK1y9bQt+igIxGtTC9u/jSFjR9MKhmI00rP1fwSvERt+5ddE82544l0XH5mzXozQVUy2Tw==", "dev": true, "requires": { - "babel-runtime": "^6.11.6", - "brfs": "^1.4.0", + "babel-runtime": "^6.26.0", + "brfs": "^2.0.0", "brotli": "^1.2.0", - "browserify-optional": "^1.0.0", - "clone": "^1.0.1", + "browserify-optional": "^1.0.1", + "clone": "^1.0.4", "deep-equal": "^1.0.0", - "dfa": "^1.0.0", + "dfa": "^1.2.0", "restructure": "^0.5.3", "tiny-inflate": "^1.0.2", - "unicode-properties": "^1.0.0", + "unicode-properties": "^1.2.2", "unicode-trie": "^0.3.0" }, "dependencies": { - "brfs": { - "version": "1.6.1", - "resolved": "https://registry.npmjs.org/brfs/-/brfs-1.6.1.tgz", - "integrity": "sha512-OfZpABRQQf+Xsmju8XE9bDjs+uU4vLREGolP7bDgcpsI17QREyZ4Bl+2KLxxx1kCgA0fAIhKQBaBYh+PEcCqYQ==", - "dev": true, - "requires": { - "quote-stream": "^1.0.1", - "resolve": "^1.1.5", - "static-module": "^2.2.0", - "through2": "^2.0.0" - } - }, "clone": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/clone/-/clone-1.0.4.tgz", "integrity": "sha1-2jCcwmPfFZlMaIypAheco8fNfH4=", "dev": true }, - "magic-string": { - "version": "0.22.5", - "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.22.5.tgz", - "integrity": "sha512-oreip9rJZkzvA8Qzk9HFs8fZGF/u7H/gtrE8EN6RjKJ9kh2HlC+yQ2QezifqTZfGyiuAV0dRv5a+y/8gBb1m9w==", - "dev": true, - "requires": { - "vlq": "^0.2.2" - } - }, - "merge-source-map": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/merge-source-map/-/merge-source-map-1.0.4.tgz", - "integrity": "sha1-pd5GU42uhNQRTMXqArR3KmNGcB8=", - "dev": true, - "requires": { - "source-map": "^0.5.6" - } - }, - "object-inspect": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.4.1.tgz", - "integrity": "sha512-wqdhLpfCUbEsoEwl3FXwGyv8ief1k/1aUdIPCqVnupM6e8l63BEJdiF/0swtn04/8p05tG/T0FrpTlfwvljOdw==", - "dev": true - }, "pako": { "version": "0.2.9", "resolved": "https://registry.npmjs.org/pako/-/pako-0.2.9.tgz", "integrity": "sha1-8/dSL073gjSNqBYbrZ7P1Rv4OnU=", "dev": true }, - "source-map": { - "version": "0.5.7", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", - "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", - "dev": true - }, - "static-module": { - "version": "2.2.5", - "resolved": "https://registry.npmjs.org/static-module/-/static-module-2.2.5.tgz", - "integrity": "sha512-D8vv82E/Kpmz3TXHKG8PPsCPg+RAX6cbCOyvjM6x04qZtQ47EtJFVwRsdov3n5d6/6ynrOY9XB4JkaZwB2xoRQ==", - "dev": true, - "requires": { - "concat-stream": "~1.6.0", - "convert-source-map": "^1.5.1", - "duplexer2": "~0.1.4", - "escodegen": "~1.9.0", - "falafel": "^2.1.0", - "has": "^1.0.1", - "magic-string": "^0.22.4", - "merge-source-map": "1.0.4", - "object-inspect": "~1.4.0", - "quote-stream": "~1.0.2", - "readable-stream": "~2.3.3", - "shallow-copy": "~0.0.1", - "static-eval": "^2.0.0", - "through2": "~2.0.3" - } - }, "unicode-trie": { "version": "0.3.1", "resolved": "https://registry.npmjs.org/unicode-trie/-/unicode-trie-0.3.1.tgz", @@ -7342,12 +7732,6 @@ "for-in": "^1.0.1" } }, - "foreach": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/foreach/-/foreach-2.0.5.tgz", - "integrity": "sha1-C+4AUBiusmDQo6865ljdATbsG5k=", - "dev": true - }, "forever-agent": { "version": "0.6.1", "resolved": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz", @@ -7489,6 +7873,23 @@ "integrity": "sha1-3BXKHGcjh8p2vTesCjlbogQqLCg=", "dev": true }, + "getos": { + "version": "3.1.4", + "resolved": "https://registry.npmjs.org/getos/-/getos-3.1.4.tgz", + "integrity": "sha512-UORPzguEB/7UG5hqiZai8f0vQ7hzynMQyJLxStoQ8dPGAcmgsfXOPA4iE/fGtweHYkK+z4zc9V0g+CIFRf5HYw==", + "dev": true, + "requires": { + "async": "^3.1.0" + }, + "dependencies": { + "async": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/async/-/async-3.2.0.tgz", + "integrity": "sha512-TR2mEZFVOj2pLStYxLht7TyfuRzaydfpxr3k9RpHIzMgw7A64dzsdqCxH1WJyQdoe8T10nDXd9wnEigmiuHIZw==", + "dev": true + } + } + }, "getpass": { "version": "0.1.7", "resolved": "https://registry.npmjs.org/getpass/-/getpass-0.1.7.tgz", @@ -7538,6 +7939,15 @@ "integrity": "sha1-jFoUlNIGbFcMw7/kSWF1rMTVAqs=", "dev": true }, + "global-dirs": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/global-dirs/-/global-dirs-0.1.1.tgz", + "integrity": "sha1-sxnA3UYH81PzvpzKTHL8FIxJ9EU=", + "dev": true, + "requires": { + "ini": "^1.3.4" + } + }, "global-modules": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/global-modules/-/global-modules-1.0.0.tgz", @@ -7610,15 +8020,16 @@ "dev": true }, "handlebars": { - "version": "4.7.3", - "resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.7.3.tgz", - "integrity": "sha512-SRGwSYuNfx8DwHD/6InAPzD6RgeruWLT+B8e8a7gGs8FWgHzlExpTFMEq2IA6QpAfOClpKHy6+8IqTjeBCu6Kg==", + "version": "4.7.6", + "resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.7.6.tgz", + "integrity": "sha512-1f2BACcBfiwAfStCKZNrUCgqNZkGsAT7UM3kkYtXuLo0KnaVfjKOyf7PRzB6++aK9STyT1Pd2ZCPe3EGOXleXA==", "dev": true, "requires": { + "minimist": "^1.2.5", "neo-async": "^2.6.0", - "optimist": "^0.6.1", "source-map": "^0.6.1", - "uglify-js": "^3.1.4" + "uglify-js": "^3.1.4", + "wordwrap": "^1.0.0" }, "dependencies": { "source-map": { @@ -7795,9 +8206,9 @@ } }, "html-entities": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/html-entities/-/html-entities-1.2.1.tgz", - "integrity": "sha1-DfKTUfByEWNRXfueVUPl9u7VFi8=", + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/html-entities/-/html-entities-1.3.1.tgz", + "integrity": "sha512-rhE/4Z3hIhzHAUKbW8jVcCyuT5oJCXXqhN/6mXXVCpzTmvJnoH2HL/bt3EZ6p55jbFJBeAe1ZNpL5BugLujxNA==", "dev": true }, "html-escaper": { @@ -8235,12 +8646,6 @@ "dev": true, "optional": true }, - "immediate": { - "version": "3.0.6", - "resolved": "https://registry.npmjs.org/immediate/-/immediate-3.0.6.tgz", - "integrity": "sha1-nbHb0Pr43m++D13V5Wu2BigN5ps=", - "dev": true - }, "import-cwd": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/import-cwd/-/import-cwd-2.1.0.tgz", @@ -8285,6 +8690,12 @@ "integrity": "sha1-khi5srkoojixPcT7a21XbyMUU+o=", "dev": true }, + "indent-string": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-3.2.0.tgz", + "integrity": "sha1-Sl/W0nzDMvN+VBmlBNu4NxBckok=", + "dev": true + }, "infer-owner": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/infer-owner/-/infer-owner-1.0.4.tgz", @@ -8597,6 +9008,27 @@ "is-extglob": "^2.1.1" } }, + "is-installed-globally": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/is-installed-globally/-/is-installed-globally-0.1.0.tgz", + "integrity": "sha1-Df2Y9akRFxbdU13aZJL2e/PSWoA=", + "dev": true, + "requires": { + "global-dirs": "^0.1.0", + "is-path-inside": "^1.0.0" + }, + "dependencies": { + "is-path-inside": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-1.0.1.tgz", + "integrity": "sha1-jvW33lBDej/cprToZe96pVy0gDY=", + "dev": true, + "requires": { + "path-is-inside": "^1.0.1" + } + } + } + }, "is-negated-glob": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/is-negated-glob/-/is-negated-glob-1.0.0.tgz", @@ -8609,6 +9041,15 @@ "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", "dev": true }, + "is-observable": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-observable/-/is-observable-1.1.0.tgz", + "integrity": "sha512-NqCa4Sa2d+u7BWc6CukaObG3Fh+CU9bvixbpcXYhy2VvYS7vVGIdAgnIS5Ks3A/cqk4rebLJ9s8zBstT2aKnIA==", + "dev": true, + "requires": { + "symbol-observable": "^1.1.0" + } + }, "is-path-cwd": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/is-path-cwd/-/is-path-cwd-2.2.0.tgz", @@ -8649,9 +9090,9 @@ } }, "is-promise": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-2.1.0.tgz", - "integrity": "sha1-eaKp7OfwlugPNtKy87wWwf9L8/o=", + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-2.2.2.tgz", + "integrity": "sha512-+lP4/6lKUBfQjZ2pdxThZvLUAafmZb8OAxFb8XXtiQmS35INgr85hdOGoEs124ez1FCnZJt6jau/T+alh58QFQ==", "dev": true }, "is-regex": { @@ -8775,9 +9216,9 @@ "dev": true }, "make-dir": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.0.2.tgz", - "integrity": "sha512-rYKABKutXa6vXTXhoV18cBE7PaewPXHe/Bdq4v+ZLMhxbWApkFFplT0LcbMW+6BbjnQXzZ/sAvSE/JdguApG5w==", + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz", + "integrity": "sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==", "dev": true, "requires": { "semver": "^6.0.0" @@ -8814,64 +9255,15 @@ } }, "istanbul-reports": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.0.1.tgz", - "integrity": "sha512-Vm9xwCiQ8t2cNNnckyeAV0UdxKpcQUz4nMxsBvIu8n2kmPSiyb5uaF/8LpmKr+yqL/MdOXaX2Nmdo4Qyxium9Q==", + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.0.2.tgz", + "integrity": "sha512-9tZvz7AiR3PEDNGiV9vIouQ/EAcqMXFmkcA1CDFTwOB98OZVDL0PH9glHotf5Ugp6GCOTypfzGWI/OqjWNCRUw==", "dev": true, "requires": { "html-escaper": "^2.0.0", "istanbul-lib-report": "^3.0.0" } }, - "jasmine": { - "version": "2.8.0", - "resolved": "https://registry.npmjs.org/jasmine/-/jasmine-2.8.0.tgz", - "integrity": "sha1-awicChFXax8W3xG4AUbZHU6Lij4=", - "dev": true, - "requires": { - "exit": "^0.1.2", - "glob": "^7.0.6", - "jasmine-core": "~2.8.0" - }, - "dependencies": { - "jasmine-core": { - "version": "2.8.0", - "resolved": "https://registry.npmjs.org/jasmine-core/-/jasmine-core-2.8.0.tgz", - "integrity": "sha1-vMl5rh+f0FcB5F5S5l06XWPxok4=", - "dev": true - } - } - }, - "jasmine-core": { - "version": "3.5.0", - "resolved": "https://registry.npmjs.org/jasmine-core/-/jasmine-core-3.5.0.tgz", - "integrity": "sha512-nCeAiw37MIMA9w9IXso7bRaLl+c/ef3wnxsoSAlYrzS+Ot0zTG6nU8G/cIfGkqpkjX2wNaIW9RFG0TwIFnG6bA==", - "dev": true - }, - "jasmine-fail-fast": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/jasmine-fail-fast/-/jasmine-fail-fast-2.0.1.tgz", - "integrity": "sha512-En8ONwvDQOV+jyiZEZvbvUSLWSdJFj9HiWjhLdGq/V/gxs4XyST730ooe928BbRxv4bfy05OpykKuoOU4aLC5w==", - "dev": true, - "requires": { - "lodash": "^4.17.15" - } - }, - "jasmine-spec-reporter": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/jasmine-spec-reporter/-/jasmine-spec-reporter-5.0.1.tgz", - "integrity": "sha512-RrOZ+bSPnbk1/9KKs5lm0Nl0cqDCh/XXVlCmu3nkhEJH6HTDh4hoJZu3q8e9aq37C0eXEf/JEJnYy+t4m3arZQ==", - "dev": true, - "requires": { - "colors": "1.4.0" - } - }, - "jasminewd2": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/jasminewd2/-/jasminewd2-2.2.0.tgz", - "integrity": "sha1-43zwsX8ZnM4jvqcbIDk5Uka07E4=", - "dev": true - }, "jest": { "version": "25.2.4", "resolved": "https://registry.npmjs.org/jest/-/jest-25.2.4.tgz", @@ -8974,21 +9366,21 @@ "dev": true }, "jest-cli": { - "version": "25.2.4", - "resolved": "https://registry.npmjs.org/jest-cli/-/jest-cli-25.2.4.tgz", - "integrity": "sha512-zeY2pRDWKj2LZudIncvvguwLMEdcnJqc2jJbwza1beqi80qqLvkPF/BjbFkK2sIV3r+mfTJS+7ITrvK6pCdRjg==", + "version": "25.4.0", + "resolved": "https://registry.npmjs.org/jest-cli/-/jest-cli-25.4.0.tgz", + "integrity": "sha512-usyrj1lzCJZMRN1r3QEdnn8e6E6yCx/QN7+B1sLoA68V7f3WlsxSSQfy0+BAwRiF4Hz2eHauf11GZG3PIfWTXQ==", "dev": true, "requires": { - "@jest/core": "^25.2.4", - "@jest/test-result": "^25.2.4", - "@jest/types": "^25.2.3", + "@jest/core": "^25.4.0", + "@jest/test-result": "^25.4.0", + "@jest/types": "^25.4.0", "chalk": "^3.0.0", "exit": "^0.1.2", "import-local": "^3.0.2", "is-ci": "^2.0.0", - "jest-config": "^25.2.4", - "jest-util": "^25.2.3", - "jest-validate": "^25.2.3", + "jest-config": "^25.4.0", + "jest-util": "^25.4.0", + "jest-validate": "^25.4.0", "prompts": "^2.0.1", "realpath-native": "^2.0.0", "yargs": "^15.3.1" @@ -9004,9 +9396,9 @@ } }, "p-limit": { - "version": "2.2.2", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.2.2.tgz", - "integrity": "sha512-WGR+xHecKTr7EbUEhyLSh5Dube9JtdiG78ufaeLxTgpudf/20KqyMioIUZJAezlTIi6evxuoUs9YXc11cU+yzQ==", + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", "dev": true, "requires": { "p-try": "^2.0.0" @@ -9129,9 +9521,9 @@ } }, "yargs-parser": { - "version": "18.1.2", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-18.1.2.tgz", - "integrity": "sha512-hlIPNR3IzC1YuL1c2UwwDKpXlNFBqD1Fswwh1khz5+d8Cq/8yc/Mn0i+rQXduu8hcrFKvO7Eryk+09NecTQAAQ==", + "version": "18.1.3", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-18.1.3.tgz", + "integrity": "sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ==", "dev": true, "requires": { "camelcase": "^5.0.0", @@ -9151,20 +9543,20 @@ } }, "jest-changed-files": { - "version": "25.2.3", - "resolved": "https://registry.npmjs.org/jest-changed-files/-/jest-changed-files-25.2.3.tgz", - "integrity": "sha512-EFxy94dvvbqRB36ezIPLKJ4fDIC+jAdNs8i8uTwFpaXd6H3LVc3ova1lNS4ZPWk09OCR2vq5kSdSQgar7zMORg==", + "version": "25.4.0", + "resolved": "https://registry.npmjs.org/jest-changed-files/-/jest-changed-files-25.4.0.tgz", + "integrity": "sha512-VR/rfJsEs4BVMkwOTuStRyS630fidFVekdw/lBaBQjx9KK3VZFOZ2c0fsom2fRp8pMCrCTP6LGna00o/DXGlqA==", "dev": true, "requires": { - "@jest/types": "^25.2.3", + "@jest/types": "^25.4.0", "execa": "^3.2.0", "throat": "^5.0.0" }, "dependencies": { "cross-spawn": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.1.tgz", - "integrity": "sha512-u7v4o84SwFpD32Z8IIcPZ6z1/ie24O6RU3RbtL5Y316l3KuHVPx9ItBgWQ6VlfAFnRnTtMUrsQ9MUUTuEZjogg==", + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.2.tgz", + "integrity": "sha512-PD6G8QG3S4FK/XCGFbEQrDqO2AnMMsy0meR7lerlIOHAAbkuavGU/pOqprrlvfTNjvowivTeBsjebAL0NSoMxw==", "dev": true, "requires": { "path-key": "^3.1.0", @@ -9253,28 +9645,28 @@ } }, "jest-config": { - "version": "25.2.4", - "resolved": "https://registry.npmjs.org/jest-config/-/jest-config-25.2.4.tgz", - "integrity": "sha512-fxy3nIpwJqOUQJRVF/q+pNQb6dv5b9YufOeCbpPZJ/md1zXpiupbhfehpfODhnKOfqbzSiigtSLzlWWmbRxnqQ==", + "version": "25.4.0", + "resolved": "https://registry.npmjs.org/jest-config/-/jest-config-25.4.0.tgz", + "integrity": "sha512-egT9aKYxMyMSQV1aqTgam0SkI5/I2P9qrKexN5r2uuM2+68ypnc+zPGmfUxK7p1UhE7dYH9SLBS7yb+TtmT1AA==", "dev": true, "requires": { "@babel/core": "^7.1.0", - "@jest/test-sequencer": "^25.2.4", - "@jest/types": "^25.2.3", - "babel-jest": "^25.2.4", + "@jest/test-sequencer": "^25.4.0", + "@jest/types": "^25.4.0", + "babel-jest": "^25.4.0", "chalk": "^3.0.0", "deepmerge": "^4.2.2", "glob": "^7.1.1", - "jest-environment-jsdom": "^25.2.4", - "jest-environment-node": "^25.2.4", - "jest-get-type": "^25.2.1", - "jest-jasmine2": "^25.2.4", - "jest-regex-util": "^25.2.1", - "jest-resolve": "^25.2.3", - "jest-util": "^25.2.3", - "jest-validate": "^25.2.3", + "jest-environment-jsdom": "^25.4.0", + "jest-environment-node": "^25.4.0", + "jest-get-type": "^25.2.6", + "jest-jasmine2": "^25.4.0", + "jest-regex-util": "^25.2.6", + "jest-resolve": "^25.4.0", + "jest-util": "^25.4.0", + "jest-validate": "^25.4.0", "micromatch": "^4.0.2", - "pretty-format": "^25.2.3", + "pretty-format": "^25.4.0", "realpath-native": "^2.0.0" }, "dependencies": { @@ -9335,15 +9727,15 @@ } }, "jest-diff": { - "version": "25.2.3", - "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-25.2.3.tgz", - "integrity": "sha512-VtZ6LAQtaQpFsmEzps15dQc5ELbJxy4L2DOSo2Ev411TUEtnJPkAMD7JneVypeMJQ1y3hgxN9Ao13n15FAnavg==", + "version": "25.4.0", + "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-25.4.0.tgz", + "integrity": "sha512-kklLbJVXW0y8UKOWOdYhI6TH5MG6QAxrWiBMgQaPIuhj3dNFGirKCd+/xfplBXICQ7fI+3QcqHm9p9lWu1N6ug==", "dev": true, "requires": { "chalk": "^3.0.0", - "diff-sequences": "^25.2.1", - "jest-get-type": "^25.2.1", - "pretty-format": "^25.2.3" + "diff-sequences": "^25.2.6", + "jest-get-type": "^25.2.6", + "pretty-format": "^25.4.0" }, "dependencies": { "ansi-styles": { @@ -9393,25 +9785,25 @@ } }, "jest-docblock": { - "version": "25.2.3", - "resolved": "https://registry.npmjs.org/jest-docblock/-/jest-docblock-25.2.3.tgz", - "integrity": "sha512-d3/tmjLLrH5fpRGmIm3oFa3vOaD/IjPxtXVOrfujpfJ9y1tCDB1x/tvunmdOVAyF03/xeMwburl6ITbiQT1mVA==", + "version": "25.3.0", + "resolved": "https://registry.npmjs.org/jest-docblock/-/jest-docblock-25.3.0.tgz", + "integrity": "sha512-aktF0kCar8+zxRHxQZwxMy70stc9R1mOmrLsT5VO3pIT0uzGRSDAXxSlz4NqQWpuLjPpuMhPRl7H+5FRsvIQAg==", "dev": true, "requires": { "detect-newline": "^3.0.0" } }, "jest-each": { - "version": "25.2.3", - "resolved": "https://registry.npmjs.org/jest-each/-/jest-each-25.2.3.tgz", - "integrity": "sha512-RTlmCjsBDK2c9T5oO4MqccA3/5Y8BUtiEy7OOQik1iyCgdnNdHbI0pNEpyapZPBG0nlvZ4mIu7aY6zNUvLraAQ==", + "version": "25.4.0", + "resolved": "https://registry.npmjs.org/jest-each/-/jest-each-25.4.0.tgz", + "integrity": "sha512-lwRIJ8/vQU/6vq3nnSSUw1Y3nz5tkYSFIywGCZpUBd6WcRgpn8NmJoQICojbpZmsJOJNHm0BKdyuJ6Xdx+eDQQ==", "dev": true, "requires": { - "@jest/types": "^25.2.3", + "@jest/types": "^25.4.0", "chalk": "^3.0.0", - "jest-get-type": "^25.2.1", - "jest-util": "^25.2.3", - "pretty-format": "^25.2.3" + "jest-get-type": "^25.2.6", + "jest-util": "^25.4.0", + "pretty-format": "^25.4.0" }, "dependencies": { "ansi-styles": { @@ -9461,53 +9853,53 @@ } }, "jest-environment-jsdom": { - "version": "25.2.4", - "resolved": "https://registry.npmjs.org/jest-environment-jsdom/-/jest-environment-jsdom-25.2.4.tgz", - "integrity": "sha512-5dm+tNwrLmhELdjAwiQnVGf/U9iFMWdTL4/wyrMg2HU6RQnCiuxpWbIigLHUhuP1P2Ak0F4k3xhjrikboKyShA==", + "version": "25.4.0", + "resolved": "https://registry.npmjs.org/jest-environment-jsdom/-/jest-environment-jsdom-25.4.0.tgz", + "integrity": "sha512-KTitVGMDrn2+pt7aZ8/yUTuS333w3pWt1Mf88vMntw7ZSBNDkRS6/4XLbFpWXYfWfp1FjcjQTOKzbK20oIehWQ==", "dev": true, "requires": { - "@jest/environment": "^25.2.4", - "@jest/fake-timers": "^25.2.4", - "@jest/types": "^25.2.3", - "jest-mock": "^25.2.3", - "jest-util": "^25.2.3", + "@jest/environment": "^25.4.0", + "@jest/fake-timers": "^25.4.0", + "@jest/types": "^25.4.0", + "jest-mock": "^25.4.0", + "jest-util": "^25.4.0", "jsdom": "^15.2.1" } }, "jest-environment-node": { - "version": "25.2.4", - "resolved": "https://registry.npmjs.org/jest-environment-node/-/jest-environment-node-25.2.4.tgz", - "integrity": "sha512-Jkc5Y8goyXPrLRHnrUlqC7P4o5zn2m4zw6qWoRJ59kxV1f2a5wK+TTGhrhCwnhW/Ckpdl/pm+LufdvhJkvJbiw==", + "version": "25.4.0", + "resolved": "https://registry.npmjs.org/jest-environment-node/-/jest-environment-node-25.4.0.tgz", + "integrity": "sha512-wryZ18vsxEAKFH7Z74zi/y/SyI1j6UkVZ6QsllBuT/bWlahNfQjLNwFsgh/5u7O957dYFoXj4yfma4n4X6kU9A==", "dev": true, "requires": { - "@jest/environment": "^25.2.4", - "@jest/fake-timers": "^25.2.4", - "@jest/types": "^25.2.3", - "jest-mock": "^25.2.3", - "jest-util": "^25.2.3", + "@jest/environment": "^25.4.0", + "@jest/fake-timers": "^25.4.0", + "@jest/types": "^25.4.0", + "jest-mock": "^25.4.0", + "jest-util": "^25.4.0", "semver": "^6.3.0" } }, "jest-get-type": { - "version": "25.2.1", - "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-25.2.1.tgz", - "integrity": "sha512-EYjTiqcDTCRJDcSNKbLTwn/LcDPEE7ITk8yRMNAOjEsN6yp+Uu+V1gx4djwnuj/DvWg0YGmqaBqPVGsPxlvE7w==", + "version": "25.2.6", + "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-25.2.6.tgz", + "integrity": "sha512-DxjtyzOHjObRM+sM1knti6or+eOgcGU4xVSb2HNP1TqO4ahsT+rqZg+nyqHWJSvWgKC5cG3QjGFBqxLghiF/Ig==", "dev": true }, "jest-haste-map": { - "version": "25.2.3", - "resolved": "https://registry.npmjs.org/jest-haste-map/-/jest-haste-map-25.2.3.tgz", - "integrity": "sha512-pAP22OHtPr4qgZlJJFks2LLgoQUr4XtM1a+F5UaPIZNiCRnePA0hM3L7aiJ0gzwiNIYwMTfKRwG/S1L28J3A3A==", + "version": "25.4.0", + "resolved": "https://registry.npmjs.org/jest-haste-map/-/jest-haste-map-25.4.0.tgz", + "integrity": "sha512-5EoCe1gXfGC7jmXbKzqxESrgRcaO3SzWXGCnvp9BcT0CFMyrB1Q6LIsjl9RmvmJGQgW297TCfrdgiy574Rl9HQ==", "dev": true, "requires": { - "@jest/types": "^25.2.3", + "@jest/types": "^25.4.0", "anymatch": "^3.0.3", "fb-watchman": "^2.0.0", "fsevents": "2.1.2", "graceful-fs": "^4.2.3", - "jest-serializer": "^25.2.1", - "jest-util": "^25.2.3", - "jest-worker": "^25.2.1", + "jest-serializer": "^25.2.6", + "jest-util": "^25.4.0", + "jest-worker": "^25.4.0", "micromatch": "^4.0.2", "sane": "^4.0.3", "walker": "^1.0.7", @@ -9524,9 +9916,9 @@ "dev": true }, "jest-worker": { - "version": "25.2.1", - "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-25.2.1.tgz", - "integrity": "sha512-IHnpekk8H/hCUbBlfeaPZzU6v75bqwJp3n4dUrQuQOAgOneI4tx3jV2o8pvlXnDfcRsfkFIUD//HWXpCmR+evQ==", + "version": "25.4.0", + "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-25.4.0.tgz", + "integrity": "sha512-ghAs/1FtfYpMmYQ0AHqxV62XPvKdUDIBBApMZfly+E9JEmYh2K45G0R5dWxx986RN12pRCxsViwQVtGl+N4whw==", "dev": true, "requires": { "merge-stream": "^2.0.0", @@ -9564,27 +9956,27 @@ } }, "jest-jasmine2": { - "version": "25.2.4", - "resolved": "https://registry.npmjs.org/jest-jasmine2/-/jest-jasmine2-25.2.4.tgz", - "integrity": "sha512-juoKrmNmLwaheNbAg71SuUF9ovwUZCFNTpKVhvCXWk+SSeORcIUMptKdPCoLXV3D16htzhTSKmNxnxSk4SrTjA==", + "version": "25.4.0", + "resolved": "https://registry.npmjs.org/jest-jasmine2/-/jest-jasmine2-25.4.0.tgz", + "integrity": "sha512-QccxnozujVKYNEhMQ1vREiz859fPN/XklOzfQjm2j9IGytAkUbSwjFRBtQbHaNZ88cItMpw02JnHGsIdfdpwxQ==", "dev": true, "requires": { "@babel/traverse": "^7.1.0", - "@jest/environment": "^25.2.4", - "@jest/source-map": "^25.2.1", - "@jest/test-result": "^25.2.4", - "@jest/types": "^25.2.3", + "@jest/environment": "^25.4.0", + "@jest/source-map": "^25.2.6", + "@jest/test-result": "^25.4.0", + "@jest/types": "^25.4.0", "chalk": "^3.0.0", "co": "^4.6.0", - "expect": "^25.2.4", + "expect": "^25.4.0", "is-generator-fn": "^2.0.0", - "jest-each": "^25.2.3", - "jest-matcher-utils": "^25.2.3", - "jest-message-util": "^25.2.4", - "jest-runtime": "^25.2.4", - "jest-snapshot": "^25.2.4", - "jest-util": "^25.2.3", - "pretty-format": "^25.2.3", + "jest-each": "^25.4.0", + "jest-matcher-utils": "^25.4.0", + "jest-message-util": "^25.4.0", + "jest-runtime": "^25.4.0", + "jest-snapshot": "^25.4.0", + "jest-util": "^25.4.0", + "pretty-format": "^25.4.0", "throat": "^5.0.0" }, "dependencies": { @@ -9635,25 +10027,25 @@ } }, "jest-leak-detector": { - "version": "25.2.3", - "resolved": "https://registry.npmjs.org/jest-leak-detector/-/jest-leak-detector-25.2.3.tgz", - "integrity": "sha512-yblCMPE7NJKl7778Cf/73yyFWAas5St0iiEBwq7RDyaz6Xd4WPFnPz2j7yDb/Qce71A1IbDoLADlcwD8zT74Aw==", + "version": "25.4.0", + "resolved": "https://registry.npmjs.org/jest-leak-detector/-/jest-leak-detector-25.4.0.tgz", + "integrity": "sha512-7Y6Bqfv2xWsB+7w44dvZuLs5SQ//fzhETgOGG7Gq3TTGFdYvAgXGwV8z159RFZ6fXiCPm/szQ90CyfVos9JIFQ==", "dev": true, "requires": { - "jest-get-type": "^25.2.1", - "pretty-format": "^25.2.3" + "jest-get-type": "^25.2.6", + "pretty-format": "^25.4.0" } }, "jest-matcher-utils": { - "version": "25.2.3", - "resolved": "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-25.2.3.tgz", - "integrity": "sha512-ZmiXiwQRVM9MoKjGMP5YsGGk2Th5ncyRxfXKz5AKsmU8m43kgNZirckVzaP61MlSa9LKmXbevdYqVp1ZKAw2Rw==", + "version": "25.4.0", + "resolved": "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-25.4.0.tgz", + "integrity": "sha512-yPMdtj7YDgXhnGbc66bowk8AkQ0YwClbbwk3Kzhn5GVDrciiCr27U4NJRbrqXbTdtxjImONITg2LiRIw650k5A==", "dev": true, "requires": { "chalk": "^3.0.0", - "jest-diff": "^25.2.3", - "jest-get-type": "^25.2.1", - "pretty-format": "^25.2.3" + "jest-diff": "^25.4.0", + "jest-get-type": "^25.2.6", + "pretty-format": "^25.4.0" }, "dependencies": { "ansi-styles": { @@ -9703,14 +10095,13 @@ } }, "jest-message-util": { - "version": "25.2.4", - "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-25.2.4.tgz", - "integrity": "sha512-9wWMH3Bf+GVTv0GcQLmH/FRr0x0toptKw9TA8U5YFLVXx7Tq9pvcNzTyJrcTJ+wLqNbMPPJlJNft4MnlcrtF5Q==", + "version": "25.4.0", + "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-25.4.0.tgz", + "integrity": "sha512-LYY9hRcVGgMeMwmdfh9tTjeux1OjZHMusq/E5f3tJN+dAoVVkJtq5ZUEPIcB7bpxDUt2zjUsrwg0EGgPQ+OhXQ==", "dev": true, "requires": { "@babel/code-frame": "^7.0.0", - "@jest/test-result": "^25.2.4", - "@jest/types": "^25.2.3", + "@jest/types": "^25.4.0", "@types/stack-utils": "^1.0.1", "chalk": "^3.0.0", "micromatch": "^4.0.2", @@ -9781,12 +10172,12 @@ } }, "jest-mock": { - "version": "25.2.3", - "resolved": "https://registry.npmjs.org/jest-mock/-/jest-mock-25.2.3.tgz", - "integrity": "sha512-xlf+pyY0j47zoCs8zGGOGfWyxxLximE8YFOfEK8s4FruR8DtM/UjNj61um+iDuMAFEBDe1bhCXkqiKoCmWjJzg==", + "version": "25.4.0", + "resolved": "https://registry.npmjs.org/jest-mock/-/jest-mock-25.4.0.tgz", + "integrity": "sha512-MdazSfcYAUjJjuVTTnusLPzE0pE4VXpOUzWdj8sbM+q6abUjm3bATVPXFqTXrxSieR8ocpvQ9v/QaQCftioQFg==", "dev": true, "requires": { - "@jest/types": "^25.2.3" + "@jest/types": "^25.4.0" } }, "jest-pnp-resolver": { @@ -9806,23 +10197,25 @@ } }, "jest-regex-util": { - "version": "25.2.1", - "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-25.2.1.tgz", - "integrity": "sha512-wroFVJw62LdqTdkL508ZLV82FrJJWVJMIuYG7q4Uunl1WAPTf4ftPKrqqfec4SvOIlvRZUdEX2TFpWR356YG/w==", + "version": "25.2.6", + "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-25.2.6.tgz", + "integrity": "sha512-KQqf7a0NrtCkYmZZzodPftn7fL1cq3GQAFVMn5Hg8uKx/fIenLEobNanUxb7abQ1sjADHBseG/2FGpsv/wr+Qw==", "dev": true }, "jest-resolve": { - "version": "25.2.3", - "resolved": "https://registry.npmjs.org/jest-resolve/-/jest-resolve-25.2.3.tgz", - "integrity": "sha512-1vZMsvM/DBH258PnpUNSXIgtzpYz+vCVCj9+fcy4akZl4oKbD+9hZSlfe9RIDpU0Fc28ozHQrmwX3EqFRRIHGg==", + "version": "25.4.0", + "resolved": "https://registry.npmjs.org/jest-resolve/-/jest-resolve-25.4.0.tgz", + "integrity": "sha512-wOsKqVDFWUiv8BtLMCC6uAJ/pHZkfFgoBTgPtmYlsprAjkxrr2U++ZnB3l5ykBMd2O24lXvf30SMAjJIW6k2aA==", "dev": true, "requires": { - "@jest/types": "^25.2.3", + "@jest/types": "^25.4.0", "browser-resolve": "^1.11.3", "chalk": "^3.0.0", "jest-pnp-resolver": "^1.2.1", + "read-pkg-up": "^7.0.1", "realpath-native": "^2.0.0", - "resolve": "^1.15.1" + "resolve": "^1.15.1", + "slash": "^3.0.0" }, "dependencies": { "ansi-styles": { @@ -9860,6 +10253,12 @@ "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", "dev": true }, + "slash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", + "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", + "dev": true + }, "supports-color": { "version": "7.1.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.1.0.tgz", @@ -9872,39 +10271,39 @@ } }, "jest-resolve-dependencies": { - "version": "25.2.4", - "resolved": "https://registry.npmjs.org/jest-resolve-dependencies/-/jest-resolve-dependencies-25.2.4.tgz", - "integrity": "sha512-qhUnK4PfNHzNdca7Ub1mbAqE0j5WNyMTwxBZZJjQlUrdqsiYho/QGK65FuBkZuSoYtKIIqriR9TpGrPEc3P5Gg==", + "version": "25.4.0", + "resolved": "https://registry.npmjs.org/jest-resolve-dependencies/-/jest-resolve-dependencies-25.4.0.tgz", + "integrity": "sha512-A0eoZXx6kLiuG1Ui7wITQPl04HwjLErKIJTt8GR3c7UoDAtzW84JtCrgrJ6Tkw6c6MwHEyAaLk7dEPml5pf48A==", "dev": true, "requires": { - "@jest/types": "^25.2.3", - "jest-regex-util": "^25.2.1", - "jest-snapshot": "^25.2.4" + "@jest/types": "^25.4.0", + "jest-regex-util": "^25.2.6", + "jest-snapshot": "^25.4.0" } }, "jest-runner": { - "version": "25.2.4", - "resolved": "https://registry.npmjs.org/jest-runner/-/jest-runner-25.2.4.tgz", - "integrity": "sha512-5xaIfqqxck9Wg2CV4b9KmJtf/sWO7zWQx7O+34GCLGPzoPcVmB3mZtdrQI1/jS3Reqjru9ycLjgLHSf6XoxRqA==", + "version": "25.4.0", + "resolved": "https://registry.npmjs.org/jest-runner/-/jest-runner-25.4.0.tgz", + "integrity": "sha512-wWQSbVgj2e/1chFdMRKZdvlmA6p1IPujhpLT7TKNtCSl1B0PGBGvJjCaiBal/twaU2yfk8VKezHWexM8IliBfA==", "dev": true, "requires": { - "@jest/console": "^25.2.3", - "@jest/environment": "^25.2.4", - "@jest/test-result": "^25.2.4", - "@jest/types": "^25.2.3", + "@jest/console": "^25.4.0", + "@jest/environment": "^25.4.0", + "@jest/test-result": "^25.4.0", + "@jest/types": "^25.4.0", "chalk": "^3.0.0", "exit": "^0.1.2", "graceful-fs": "^4.2.3", - "jest-config": "^25.2.4", - "jest-docblock": "^25.2.3", - "jest-haste-map": "^25.2.3", - "jest-jasmine2": "^25.2.4", - "jest-leak-detector": "^25.2.3", - "jest-message-util": "^25.2.4", - "jest-resolve": "^25.2.3", - "jest-runtime": "^25.2.4", - "jest-util": "^25.2.3", - "jest-worker": "^25.2.1", + "jest-config": "^25.4.0", + "jest-docblock": "^25.3.0", + "jest-haste-map": "^25.4.0", + "jest-jasmine2": "^25.4.0", + "jest-leak-detector": "^25.4.0", + "jest-message-util": "^25.4.0", + "jest-resolve": "^25.4.0", + "jest-runtime": "^25.4.0", + "jest-util": "^25.4.0", + "jest-worker": "^25.4.0", "source-map-support": "^0.5.6", "throat": "^5.0.0" }, @@ -9945,9 +10344,9 @@ "dev": true }, "jest-worker": { - "version": "25.2.1", - "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-25.2.1.tgz", - "integrity": "sha512-IHnpekk8H/hCUbBlfeaPZzU6v75bqwJp3n4dUrQuQOAgOneI4tx3jV2o8pvlXnDfcRsfkFIUD//HWXpCmR+evQ==", + "version": "25.4.0", + "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-25.4.0.tgz", + "integrity": "sha512-ghAs/1FtfYpMmYQ0AHqxV62XPvKdUDIBBApMZfly+E9JEmYh2K45G0R5dWxx986RN12pRCxsViwQVtGl+N4whw==", "dev": true, "requires": { "merge-stream": "^2.0.0", @@ -9966,32 +10365,32 @@ } }, "jest-runtime": { - "version": "25.2.4", - "resolved": "https://registry.npmjs.org/jest-runtime/-/jest-runtime-25.2.4.tgz", - "integrity": "sha512-6ehOUizgIghN+aV5YSrDzTZ+zJ9omgEjJbTHj3Jqes5D52XHfhzT7cSfdREwkNjRytrR7mNwZ7pRauoyNLyJ8Q==", + "version": "25.4.0", + "resolved": "https://registry.npmjs.org/jest-runtime/-/jest-runtime-25.4.0.tgz", + "integrity": "sha512-lgNJlCDULtXu9FumnwCyWlOub8iytijwsPNa30BKrSNtgoT6NUMXOPrZvsH06U6v0wgD/Igwz13nKA2wEKU2VA==", "dev": true, "requires": { - "@jest/console": "^25.2.3", - "@jest/environment": "^25.2.4", - "@jest/source-map": "^25.2.1", - "@jest/test-result": "^25.2.4", - "@jest/transform": "^25.2.4", - "@jest/types": "^25.2.3", + "@jest/console": "^25.4.0", + "@jest/environment": "^25.4.0", + "@jest/source-map": "^25.2.6", + "@jest/test-result": "^25.4.0", + "@jest/transform": "^25.4.0", + "@jest/types": "^25.4.0", "@types/yargs": "^15.0.0", "chalk": "^3.0.0", "collect-v8-coverage": "^1.0.0", "exit": "^0.1.2", "glob": "^7.1.3", "graceful-fs": "^4.2.3", - "jest-config": "^25.2.4", - "jest-haste-map": "^25.2.3", - "jest-message-util": "^25.2.4", - "jest-mock": "^25.2.3", - "jest-regex-util": "^25.2.1", - "jest-resolve": "^25.2.3", - "jest-snapshot": "^25.2.4", - "jest-util": "^25.2.3", - "jest-validate": "^25.2.3", + "jest-config": "^25.4.0", + "jest-haste-map": "^25.4.0", + "jest-message-util": "^25.4.0", + "jest-mock": "^25.4.0", + "jest-regex-util": "^25.2.6", + "jest-resolve": "^25.4.0", + "jest-snapshot": "^25.4.0", + "jest-util": "^25.4.0", + "jest-validate": "^25.4.0", "realpath-native": "^2.0.0", "slash": "^3.0.0", "strip-bom": "^4.0.0", @@ -10102,9 +10501,9 @@ } }, "p-limit": { - "version": "2.2.2", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.2.2.tgz", - "integrity": "sha512-WGR+xHecKTr7EbUEhyLSh5Dube9JtdiG78ufaeLxTgpudf/20KqyMioIUZJAezlTIi6evxuoUs9YXc11cU+yzQ==", + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", "dev": true, "requires": { "p-try": "^2.0.0" @@ -10209,9 +10608,9 @@ } }, "yargs-parser": { - "version": "18.1.2", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-18.1.2.tgz", - "integrity": "sha512-hlIPNR3IzC1YuL1c2UwwDKpXlNFBqD1Fswwh1khz5+d8Cq/8yc/Mn0i+rQXduu8hcrFKvO7Eryk+09NecTQAAQ==", + "version": "18.1.3", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-18.1.3.tgz", + "integrity": "sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ==", "dev": true, "requires": { "camelcase": "^5.0.0", @@ -10221,9 +10620,9 @@ } }, "jest-serializer": { - "version": "25.2.1", - "resolved": "https://registry.npmjs.org/jest-serializer/-/jest-serializer-25.2.1.tgz", - "integrity": "sha512-fibDi7M5ffx6c/P66IkvR4FKkjG5ldePAK1WlbNoaU4GZmIAkS9Le/frAwRUFEX0KdnisSPWf+b1RC5jU7EYJQ==", + "version": "25.2.6", + "resolved": "https://registry.npmjs.org/jest-serializer/-/jest-serializer-25.2.6.tgz", + "integrity": "sha512-RMVCfZsezQS2Ww4kB5HJTMaMJ0asmC0BHlnobQC6yEtxiFKIxohFA4QSXSabKwSggaNkqxn6Z2VwdFCjhUWuiQ==", "dev": true }, "jest-silent-reporter": { @@ -10366,24 +10765,24 @@ } }, "jest-snapshot": { - "version": "25.2.4", - "resolved": "https://registry.npmjs.org/jest-snapshot/-/jest-snapshot-25.2.4.tgz", - "integrity": "sha512-nIwpW7FZCq5p0AE3Oyqyb6jL0ENJixXzJ5/CD/XRuOqp3gS5OM3O/k+NnTrniCXxPFV4ry6s9HNfiPQBi0wcoA==", + "version": "25.4.0", + "resolved": "https://registry.npmjs.org/jest-snapshot/-/jest-snapshot-25.4.0.tgz", + "integrity": "sha512-J4CJ0X2SaGheYRZdLz9CRHn9jUknVmlks4UBeu270hPAvdsauFXOhx9SQP2JtRzhnR3cvro/9N9KP83/uvFfRg==", "dev": true, "requires": { "@babel/types": "^7.0.0", - "@jest/types": "^25.2.3", + "@jest/types": "^25.4.0", "@types/prettier": "^1.19.0", "chalk": "^3.0.0", - "expect": "^25.2.4", - "jest-diff": "^25.2.3", - "jest-get-type": "^25.2.1", - "jest-matcher-utils": "^25.2.3", - "jest-message-util": "^25.2.4", - "jest-resolve": "^25.2.3", + "expect": "^25.4.0", + "jest-diff": "^25.4.0", + "jest-get-type": "^25.2.6", + "jest-matcher-utils": "^25.4.0", + "jest-message-util": "^25.4.0", + "jest-resolve": "^25.4.0", "make-dir": "^3.0.0", "natural-compare": "^1.4.0", - "pretty-format": "^25.2.3", + "pretty-format": "^25.4.0", "semver": "^6.3.0" }, "dependencies": { @@ -10423,9 +10822,9 @@ "dev": true }, "make-dir": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.0.2.tgz", - "integrity": "sha512-rYKABKutXa6vXTXhoV18cBE7PaewPXHe/Bdq4v+ZLMhxbWApkFFplT0LcbMW+6BbjnQXzZ/sAvSE/JdguApG5w==", + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz", + "integrity": "sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==", "dev": true, "requires": { "semver": "^6.0.0" @@ -10443,12 +10842,12 @@ } }, "jest-util": { - "version": "25.2.3", - "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-25.2.3.tgz", - "integrity": "sha512-7tWiMICVSo9lNoObFtqLt9Ezt5exdFlWs5fLe1G4XLY2lEbZc814cw9t4YHScqBkWMfzth8ASHKlYBxiX2rdCw==", + "version": "25.4.0", + "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-25.4.0.tgz", + "integrity": "sha512-WSZD59sBtAUjLv1hMeKbNZXmMcrLRWcYqpO8Dz8b4CeCTZpfNQw2q9uwrYAD+BbJoLJlu4ezVPwtAmM/9/SlZA==", "dev": true, "requires": { - "@jest/types": "^25.2.3", + "@jest/types": "^25.4.0", "chalk": "^3.0.0", "is-ci": "^2.0.0", "make-dir": "^3.0.0" @@ -10490,9 +10889,9 @@ "dev": true }, "make-dir": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.0.2.tgz", - "integrity": "sha512-rYKABKutXa6vXTXhoV18cBE7PaewPXHe/Bdq4v+ZLMhxbWApkFFplT0LcbMW+6BbjnQXzZ/sAvSE/JdguApG5w==", + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz", + "integrity": "sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==", "dev": true, "requires": { "semver": "^6.0.0" @@ -10510,17 +10909,17 @@ } }, "jest-validate": { - "version": "25.2.3", - "resolved": "https://registry.npmjs.org/jest-validate/-/jest-validate-25.2.3.tgz", - "integrity": "sha512-GObn91jzU0B0Bv4cusAwjP6vnWy78hJUM8MOSz7keRfnac/ZhQWIsUjvk01IfeXNTemCwgR57EtdjQMzFZGREg==", + "version": "25.4.0", + "resolved": "https://registry.npmjs.org/jest-validate/-/jest-validate-25.4.0.tgz", + "integrity": "sha512-hvjmes/EFVJSoeP1yOl8qR8mAtMR3ToBkZeXrD/ZS9VxRyWDqQ/E1C5ucMTeSmEOGLipvdlyipiGbHJ+R1MQ0g==", "dev": true, "requires": { - "@jest/types": "^25.2.3", + "@jest/types": "^25.4.0", "camelcase": "^5.3.1", "chalk": "^3.0.0", - "jest-get-type": "^25.2.1", + "jest-get-type": "^25.2.6", "leven": "^3.1.0", - "pretty-format": "^25.2.3" + "pretty-format": "^25.4.0" }, "dependencies": { "ansi-styles": { @@ -10576,16 +10975,16 @@ } }, "jest-watcher": { - "version": "25.2.4", - "resolved": "https://registry.npmjs.org/jest-watcher/-/jest-watcher-25.2.4.tgz", - "integrity": "sha512-p7g7s3zqcy69slVzQYcphyzkB2FBmJwMbv6k6KjI5mqd6KnUnQPfQVKuVj2l+34EeuxnbXqnrjtUFmxhcL87rg==", + "version": "25.4.0", + "resolved": "https://registry.npmjs.org/jest-watcher/-/jest-watcher-25.4.0.tgz", + "integrity": "sha512-36IUfOSRELsKLB7k25j/wutx0aVuHFN6wO94gPNjQtQqFPa2rkOymmx9rM5EzbF3XBZZ2oqD9xbRVoYa2w86gw==", "dev": true, "requires": { - "@jest/test-result": "^25.2.4", - "@jest/types": "^25.2.3", + "@jest/test-result": "^25.4.0", + "@jest/types": "^25.4.0", "ansi-escapes": "^4.2.1", "chalk": "^3.0.0", - "jest-util": "^25.2.3", + "jest-util": "^25.4.0", "string-length": "^3.1.0" }, "dependencies": { @@ -10724,19 +11123,6 @@ "integrity": "sha512-add7dgA5ppRPxCFJoAGfMDi7PIBXq1RtGo7BhbLaxwrXPOmw8gq48Y9ozT01hUKy9byMjlR20EJhu5zlkErEkg==", "dev": true }, - "escodegen": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/escodegen/-/escodegen-1.14.1.tgz", - "integrity": "sha512-Bmt7NcRySdIfNPfU2ZoXDrrXsG9ZjvDxcAlMfDUgRBjLOWTuIACXPBFJH7Z+cLb40JeQco5toikyc9t9P8E9SQ==", - "dev": true, - "requires": { - "esprima": "^4.0.1", - "estraverse": "^4.2.0", - "esutils": "^2.0.2", - "optionator": "^0.8.1", - "source-map": "~0.6.1" - } - }, "parse5": { "version": "5.1.0", "resolved": "https://registry.npmjs.org/parse5/-/parse5-5.1.0.tgz", @@ -10749,13 +11135,6 @@ "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==", "dev": true }, - "source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true, - "optional": true - }, "tough-cookie": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-3.0.1.tgz", @@ -10768,9 +11147,9 @@ } }, "ws": { - "version": "7.2.3", - "resolved": "https://registry.npmjs.org/ws/-/ws-7.2.3.tgz", - "integrity": "sha512-HTDl9G9hbkNDk98naoR/cHDws7+EyYMOdL1BmjsZXRUjf7d+MficC4B7HLUPlSiho0vg+CWKrGIt/VJBd1xunQ==", + "version": "7.2.5", + "resolved": "https://registry.npmjs.org/ws/-/ws-7.2.5.tgz", + "integrity": "sha512-C34cIU4+DB2vMyAbmEKossWq2ZQDr6QEyuuCzWrM9zfw1sGc0mYiJ0UnG9zzNykt49C2Fi34hvr2vssFQRS6EA==", "dev": true } } @@ -10832,12 +11211,7 @@ "integrity": "sha512-aKS4WQjPenRxiQsC93MNfjx+nbF4PAdYzmd/1JIj8HYzqfbu86beTuNgXDzPknWk0n0uARlyewZo4s++ES36Ow==", "dev": true, "requires": { - "minimist": "1.2.5" - }, - "dependencies": { - "minimist": { - "version": "1.2.5" - } + "minimist": "^1.2.0" } }, "jsonfile": { @@ -10867,18 +11241,6 @@ "verror": "1.10.0" } }, - "jszip": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/jszip/-/jszip-3.2.2.tgz", - "integrity": "sha512-NmKajvAFQpbg3taXQXr/ccS2wcucR1AZ+NtyWp2Nq7HHVsXhcJFR8p0Baf32C2yVvBylFWVeKf+WI2AnvlPhpA==", - "dev": true, - "requires": { - "lie": "~3.3.0", - "pako": "~1.0.2", - "readable-stream": "~2.3.6", - "set-immediate-shim": "~1.0.1" - } - }, "karma-source-map-support": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/karma-source-map-support/-/karma-source-map-support-1.4.0.tgz", @@ -10900,21 +11262,18 @@ "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==", "dev": true }, - "klaw-sync": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/klaw-sync/-/klaw-sync-6.0.0.tgz", - "integrity": "sha512-nIeuVSzdCCs6TDPTqI8w1Yre34sSq7AkZ4B3sfOBbI2CgVSB4Du4aLQijFU2+lhAFCwt9+42Hel6lQNIv6AntQ==", - "dev": true, - "requires": { - "graceful-fs": "^4.1.11" - } - }, "kleur": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/kleur/-/kleur-3.0.3.tgz", "integrity": "sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w==", "dev": true }, + "lazy-ass": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/lazy-ass/-/lazy-ass-1.6.0.tgz", + "integrity": "sha1-eZllXoZGwX8In90YfRUNMyTVRRM=", + "dev": true + }, "lcid": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/lcid/-/lcid-1.0.0.tgz", @@ -10995,15 +11354,6 @@ "webpack-sources": "^1.2.0" } }, - "lie": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/lie/-/lie-3.3.0.tgz", - "integrity": "sha512-UaiMJzeWRlEujzAuw5LokY1L5ecNQYZKfmyZ9L7wDHb/p5etKaxXhohBcrw0EYby+G/NA52vRSN4N39dxHAIwQ==", - "dev": true, - "requires": { - "immediate": "~3.0.5" - } - }, "liftoff": { "version": "2.5.0", "resolved": "https://registry.npmjs.org/liftoff/-/liftoff-2.5.0.tgz", @@ -11039,6 +11389,148 @@ } } }, + "lines-and-columns": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.1.6.tgz", + "integrity": "sha1-HADHQ7QzzQpOgHWPe2SldEDZ/wA=", + "dev": true + }, + "listr": { + "version": "0.14.3", + "resolved": "https://registry.npmjs.org/listr/-/listr-0.14.3.tgz", + "integrity": "sha512-RmAl7su35BFd/xoMamRjpIE4j3v+L28o8CT5YhAXQJm1fD+1l9ngXY8JAQRJ+tFK2i5njvi0iRUKV09vPwA0iA==", + "dev": true, + "requires": { + "@samverschueren/stream-to-observable": "^0.3.0", + "is-observable": "^1.1.0", + "is-promise": "^2.1.0", + "is-stream": "^1.1.0", + "listr-silent-renderer": "^1.1.1", + "listr-update-renderer": "^0.5.0", + "listr-verbose-renderer": "^0.5.0", + "p-map": "^2.0.0", + "rxjs": "^6.3.3" + } + }, + "listr-silent-renderer": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/listr-silent-renderer/-/listr-silent-renderer-1.1.1.tgz", + "integrity": "sha1-kktaN1cVN3C/Go4/v3S4u/P5JC4=", + "dev": true + }, + "listr-update-renderer": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/listr-update-renderer/-/listr-update-renderer-0.5.0.tgz", + "integrity": "sha512-tKRsZpKz8GSGqoI/+caPmfrypiaq+OQCbd+CovEC24uk1h952lVj5sC7SqyFUm+OaJ5HN/a1YLt5cit2FMNsFA==", + "dev": true, + "requires": { + "chalk": "^1.1.3", + "cli-truncate": "^0.2.1", + "elegant-spinner": "^1.0.1", + "figures": "^1.7.0", + "indent-string": "^3.0.0", + "log-symbols": "^1.0.2", + "log-update": "^2.3.0", + "strip-ansi": "^3.0.1" + }, + "dependencies": { + "ansi-styles": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz", + "integrity": "sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4=", + "dev": true + }, + "chalk": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", + "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=", + "dev": true, + "requires": { + "ansi-styles": "^2.2.1", + "escape-string-regexp": "^1.0.2", + "has-ansi": "^2.0.0", + "strip-ansi": "^3.0.0", + "supports-color": "^2.0.0" + } + }, + "figures": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/figures/-/figures-1.7.0.tgz", + "integrity": "sha1-y+Hjr/zxzUS4DK3+0o3Hk6lwHS4=", + "dev": true, + "requires": { + "escape-string-regexp": "^1.0.5", + "object-assign": "^4.1.0" + } + }, + "log-symbols": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-1.0.2.tgz", + "integrity": "sha1-N2/3tY6jCGoPCfrMdGF+ylAeGhg=", + "dev": true, + "requires": { + "chalk": "^1.0.0" + } + }, + "supports-color": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz", + "integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=", + "dev": true + } + } + }, + "listr-verbose-renderer": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/listr-verbose-renderer/-/listr-verbose-renderer-0.5.0.tgz", + "integrity": "sha512-04PDPqSlsqIOaaaGZ+41vq5FejI9auqTInicFRndCBgE3bXG8D6W1I+mWhk+1nqbHmyhla/6BUrd5OSiHwKRXw==", + "dev": true, + "requires": { + "chalk": "^2.4.1", + "cli-cursor": "^2.1.0", + "date-fns": "^1.27.2", + "figures": "^2.0.0" + }, + "dependencies": { + "cli-cursor": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-2.1.0.tgz", + "integrity": "sha1-s12sN2R5+sw+lHR9QdDQ9SOP/LU=", + "dev": true, + "requires": { + "restore-cursor": "^2.0.0" + } + }, + "figures": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/figures/-/figures-2.0.0.tgz", + "integrity": "sha1-OrGi0qYsi/tDGgyUy3l6L84nyWI=", + "dev": true, + "requires": { + "escape-string-regexp": "^1.0.5" + } + }, + "onetime": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-2.0.1.tgz", + "integrity": "sha1-BnQoIw/WdEOyeUsiu6UotoZ5YtQ=", + "dev": true, + "requires": { + "mimic-fn": "^1.0.0" + } + }, + "restore-cursor": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-2.0.0.tgz", + "integrity": "sha1-n37ih/gv0ybU/RYpI9YhKe7g368=", + "dev": true, + "requires": { + "onetime": "^2.0.0", + "signal-exit": "^3.0.2" + } + } + } + }, "live-server": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/live-server/-/live-server-1.2.1.tgz", @@ -11407,9 +11899,10 @@ } }, "minimist": { + "version": "1.2.5", + "bundled": true, "dev": true, - "optional": true, - "version": "1.2.5" + "optional": true }, "minipass": { "version": "2.9.0", @@ -11436,12 +11929,7 @@ "dev": true, "optional": true, "requires": { - "minimist": "1.2.5" - }, - "dependencies": { - "minimist": { - "version": "1.2.5" - } + "minimist": "^1.2.5" } }, "ms": { @@ -11590,13 +12078,8 @@ "requires": { "deep-extend": "^0.6.0", "ini": "~1.3.0", - "minimist": "1.2.5", + "minimist": "^1.2.0", "strip-json-comments": "~2.0.1" - }, - "dependencies": { - "minimist": { - "version": "1.2.5" - } } }, "readable-stream": { @@ -11904,6 +12387,12 @@ "resolved": "https://registry.npmjs.org/lodash.memoize/-/lodash.memoize-4.1.2.tgz", "integrity": "sha1-vMbEmkKihA7Zl/Mj6tpezRguC/4=" }, + "lodash.once": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/lodash.once/-/lodash.once-4.1.1.tgz", + "integrity": "sha1-DdOXEhPHxW34gJd9UEyI+0cal6w=", + "dev": true + }, "lodash.sortby": { "version": "4.7.0", "resolved": "https://registry.npmjs.org/lodash.sortby/-/lodash.sortby-4.7.0.tgz", @@ -11915,10 +12404,91 @@ "resolved": "https://registry.npmjs.org/lodash.throttle/-/lodash.throttle-4.1.1.tgz", "integrity": "sha1-wj6RtxAkKscMN/HhzaknTMOb8vQ=" }, + "log-symbols": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-3.0.0.tgz", + "integrity": "sha512-dSkNGuI7iG3mfvDzUuYZyvk5dD9ocYCYzNU6CYDE6+Xqd+gwme6Z00NS3dUh8mq/73HaEtT7m6W+yUPtU6BZnQ==", + "dev": true, + "requires": { + "chalk": "^2.4.2" + } + }, + "log-update": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/log-update/-/log-update-2.3.0.tgz", + "integrity": "sha1-iDKP19HOeTiykoN0bwsbwSayRwg=", + "dev": true, + "requires": { + "ansi-escapes": "^3.0.0", + "cli-cursor": "^2.0.0", + "wrap-ansi": "^3.0.1" + }, + "dependencies": { + "ansi-escapes": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-3.2.0.tgz", + "integrity": "sha512-cBhpre4ma+U0T1oM5fXg7Dy1Jw7zzwv7lt/GoCpr+hDQJoYnKVPLL4dCvSEFMmQurOQvSrwT7SL/DAlhBI97RQ==", + "dev": true + }, + "ansi-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", + "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=", + "dev": true + }, + "cli-cursor": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-2.1.0.tgz", + "integrity": "sha1-s12sN2R5+sw+lHR9QdDQ9SOP/LU=", + "dev": true, + "requires": { + "restore-cursor": "^2.0.0" + } + }, + "onetime": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-2.0.1.tgz", + "integrity": "sha1-BnQoIw/WdEOyeUsiu6UotoZ5YtQ=", + "dev": true, + "requires": { + "mimic-fn": "^1.0.0" + } + }, + "restore-cursor": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-2.0.0.tgz", + "integrity": "sha1-n37ih/gv0ybU/RYpI9YhKe7g368=", + "dev": true, + "requires": { + "onetime": "^2.0.0", + "signal-exit": "^3.0.2" + } + }, + "strip-ansi": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", + "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=", + "dev": true, + "requires": { + "ansi-regex": "^3.0.0" + } + }, + "wrap-ansi": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-3.0.1.tgz", + "integrity": "sha1-KIoE2H7aXChuBg3+jxNc6NAH+Lo=", + "dev": true, + "requires": { + "string-width": "^2.1.1", + "strip-ansi": "^4.0.0" + } + } + } + }, "loglevel": { - "version": "1.6.7", - "resolved": "https://registry.npmjs.org/loglevel/-/loglevel-1.6.7.tgz", - "integrity": "sha512-cY2eLFrQSAfVPhCgH1s7JI73tMbg9YC3v3+ZHVW67sBS7UxWzNEk/ZBbSfLykBWHp33dqqtOv82gjhKEi81T/A==", + "version": "1.6.8", + "resolved": "https://registry.npmjs.org/loglevel/-/loglevel-1.6.8.tgz", + "integrity": "sha512-bsU7+gc9AJ2SqpzxwU3+1fedl8zAntbtC5XYlt3s2j1hJcn2PsXSmgN8TaLG/J1/2mod4+cE/3vNL70/c1RNCA==", "dev": true }, "loglevel-plugin-prefix": { @@ -12297,18 +12867,18 @@ "dev": true }, "mime-db": { - "version": "1.43.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.43.0.tgz", - "integrity": "sha512-+5dsGEEovYbT8UY9yD7eE4XTc4UwJ1jBYlgaQQF38ENsKR3wj/8q8RFZrF9WIZpB2V1ArTVFUva8sAul1NzRzQ==", + "version": "1.44.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.44.0.tgz", + "integrity": "sha512-/NOTfLrsPBVeH7YtFPgsVWveuL+4SjjYxaQ1xtM1KMFj7HdxlBlxeyNLzhyJVx7r4rZGJAZ/6lkKCitSc/Nmpg==", "dev": true }, "mime-types": { - "version": "2.1.26", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.26.tgz", - "integrity": "sha512-01paPWYgLrkqAyrlDorC1uDwl2p3qZT7yl806vW7DvDoxwXi46jsjFbg+WdwotBIk6/MbEhO/dh5aZ5sNj/dWQ==", + "version": "2.1.27", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.27.tgz", + "integrity": "sha512-JIhqnCasI9yD+SsmkquHBxTSEuZdQX5BuQnS2Vc7puQQQ+8yiP5AY5uWhpdv4YL4VM5c6iliiYWPgJ/nJQLp7w==", "dev": true, "requires": { - "mime-db": "1.43.0" + "mime-db": "1.44.0" } }, "mimic-fn": { @@ -12349,8 +12919,10 @@ } }, "minimist": { - "dev": true, - "version": "1.2.5" + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz", + "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==", + "dev": true }, "minipass": { "version": "2.9.0", @@ -12419,17 +12991,12 @@ } }, "mkdirp": { - "version": "0.5.4", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.4.tgz", - "integrity": "sha512-iG9AK/dJLtJ0XNgTuDbSyNS3zECqDlAhnQW4CsNxBG3LQJBbHmRX1egw39DmtOdCAqY+dKXV+sgPgilNWUKMVw==", + "version": "0.5.5", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.5.tgz", + "integrity": "sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ==", "dev": true, "requires": { - "minimist": "1.2.5" - }, - "dependencies": { - "minimist": { - "version": "1.2.5" - } + "minimist": "^1.2.5" } }, "mobx": { @@ -12922,6 +13489,17 @@ "which": "^1.2.9" } }, + "read-pkg": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-3.0.0.tgz", + "integrity": "sha1-nLxoaXj+5l0WwA4rGcI3/Pbjg4k=", + "dev": true, + "requires": { + "load-json-file": "^4.0.0", + "normalize-package-data": "^2.3.2", + "path-type": "^3.0.0" + } + }, "semver": { "version": "5.7.1", "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", @@ -13014,10 +13592,14 @@ "dev": true }, "object-is": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/object-is/-/object-is-1.0.2.tgz", - "integrity": "sha512-Epah+btZd5wrrfjkJZq1AOB9O6OxUQto45hzFd7lXGrpHPGE0W1k+426yrZV+k6NJOzLNNW/nVsmZdIWsAqoOQ==", - "dev": true + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/object-is/-/object-is-1.1.2.tgz", + "integrity": "sha512-5lHCz+0uufF6wZ7CRFWJN3hp8Jqblpgve06U5CMQ3f//6iDjPr2PEo9MWCjEssDsa+UZEL4PkFpr+BMop6aKzQ==", + "dev": true, + "requires": { + "define-properties": "^1.1.3", + "es-abstract": "^1.17.5" + } }, "object-keys": { "version": "1.1.1", @@ -13157,21 +13739,6 @@ "is-wsl": "^1.1.0" } }, - "optimist": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/optimist/-/optimist-0.6.1.tgz", - "integrity": "sha1-2j6nRob6IaGaERwybpDrFaAZZoY=", - "dev": true, - "requires": { - "minimist": "1.2.5", - "wordwrap": "~0.0.2" - }, - "dependencies": { - "minimist": { - "version": "1.2.5" - } - } - }, "optionator": { "version": "0.8.3", "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.8.3.tgz", @@ -13248,6 +13815,12 @@ "os-tmpdir": "^1.0.0" } }, + "ospath": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/ospath/-/ospath-1.2.2.tgz", + "integrity": "sha1-EnZjl3Sj+O8lcvf+QoDg6kVQwHs=", + "dev": true + }, "p-defer": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/p-defer/-/p-defer-1.0.0.tgz", @@ -13633,6 +14206,12 @@ } } }, + "pend": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/pend/-/pend-1.2.0.tgz", + "integrity": "sha1-elfrVQpng/kRUzH89GY9XI4AelA=", + "dev": true + }, "performance-now": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz", @@ -13710,9 +14289,9 @@ } }, "p-limit": { - "version": "2.2.2", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.2.2.tgz", - "integrity": "sha512-WGR+xHecKTr7EbUEhyLSh5Dube9JtdiG78ufaeLxTgpudf/20KqyMioIUZJAezlTIi6evxuoUs9YXc11cU+yzQ==", + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", "dev": true, "requires": { "p-try": "^2.0.0" @@ -13764,9 +14343,9 @@ } }, "p-limit": { - "version": "2.2.2", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.2.2.tgz", - "integrity": "sha512-WGR+xHecKTr7EbUEhyLSh5Dube9JtdiG78ufaeLxTgpudf/20KqyMioIUZJAezlTIi6evxuoUs9YXc11cU+yzQ==", + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", "dev": true, "requires": { "p-try": "^2.0.0" @@ -13923,13 +14502,19 @@ "integrity": "sha512-5xJQIPT8BraI7ZnaDwSbu5zLrB6vvi8hVV58yHQ+QK64qrY40dULy0HSRlQ2/2IdzeBpjhDkqdcFBnFeDEMVdg==", "dev": true }, + "pretty-bytes": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/pretty-bytes/-/pretty-bytes-5.3.0.tgz", + "integrity": "sha512-hjGrh+P926p4R4WbaB6OckyRtO0F0/lQBiT+0gnxjV+5kjPBrfVBFCsCLbMqVQeydvIoouYTCmmEURiH3R1Bdg==", + "dev": true + }, "pretty-format": { - "version": "25.2.3", - "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-25.2.3.tgz", - "integrity": "sha512-IP4+5UOAVGoyqC/DiomOeHBUKN6q00gfyT2qpAsRH64tgOKB2yF7FHJXC18OCiU0/YFierACup/zdCOWw0F/0w==", + "version": "25.4.0", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-25.4.0.tgz", + "integrity": "sha512-PI/2dpGjXK5HyXexLPZU/jw5T9Q6S1YVXxxVxco+LIqzUFHXIbKZKdUVt7GcX7QUCr31+3fzhi4gN4/wUYPVxQ==", "dev": true, "requires": { - "@jest/types": "^25.2.3", + "@jest/types": "^25.4.0", "ansi-regex": "^5.0.0", "ansi-styles": "^4.0.0", "react-is": "^16.12.0" @@ -13995,9 +14580,9 @@ "dev": true }, "cross-spawn": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.1.tgz", - "integrity": "sha512-u7v4o84SwFpD32Z8IIcPZ6z1/ie24O6RU3RbtL5Y316l3KuHVPx9ItBgWQ6VlfAFnRnTtMUrsQ9MUUTuEZjogg==", + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.2.tgz", + "integrity": "sha512-PD6G8QG3S4FK/XCGFbEQrDqO2AnMMsy0meR7lerlIOHAAbkuavGU/pOqprrlvfTNjvowivTeBsjebAL0NSoMxw==", "dev": true, "requires": { "path-key": "^3.1.0", @@ -14091,9 +14676,9 @@ "dev": true }, "p-limit": { - "version": "2.2.2", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.2.2.tgz", - "integrity": "sha512-WGR+xHecKTr7EbUEhyLSh5Dube9JtdiG78ufaeLxTgpudf/20KqyMioIUZJAezlTIi6evxuoUs9YXc11cU+yzQ==", + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", "dev": true, "requires": { "p-try": "^2.0.0" @@ -14223,205 +14808,6 @@ "genfun": "^5.0.0" } }, - "protractor": { - "version": "5.4.3", - "resolved": "https://registry.npmjs.org/protractor/-/protractor-5.4.3.tgz", - "integrity": "sha512-7pMAolv8Ah1yJIqaorDTzACtn3gk7BamVKPTeO5lqIGOrfosjPgXFx/z1dqSI+m5EeZc2GMJHPr5DYlodujDNA==", - "dev": true, - "requires": { - "@types/q": "^0.0.32", - "@types/selenium-webdriver": "^3.0.0", - "blocking-proxy": "^1.0.0", - "browserstack": "^1.5.1", - "chalk": "^1.1.3", - "glob": "^7.0.3", - "jasmine": "2.8.0", - "jasminewd2": "^2.1.0", - "optimist": "~0.6.0", - "q": "1.4.1", - "saucelabs": "^1.5.0", - "selenium-webdriver": "3.6.0", - "source-map-support": "~0.4.0", - "webdriver-js-extender": "2.1.0", - "webdriver-manager": "^12.0.6" - }, - "dependencies": { - "ansi-styles": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz", - "integrity": "sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4=", - "dev": true - }, - "chalk": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", - "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=", - "dev": true, - "requires": { - "ansi-styles": "^2.2.1", - "escape-string-regexp": "^1.0.2", - "has-ansi": "^2.0.0", - "strip-ansi": "^3.0.0", - "supports-color": "^2.0.0" - } - }, - "del": { - "version": "2.2.2", - "resolved": "https://registry.npmjs.org/del/-/del-2.2.2.tgz", - "integrity": "sha1-wSyYHQZ4RshLyvhiz/kw2Qf/0ag=", - "dev": true, - "requires": { - "globby": "^5.0.0", - "is-path-cwd": "^1.0.0", - "is-path-in-cwd": "^1.0.0", - "object-assign": "^4.0.1", - "pify": "^2.0.0", - "pinkie-promise": "^2.0.0", - "rimraf": "^2.2.8" - } - }, - "globby": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/globby/-/globby-5.0.0.tgz", - "integrity": "sha1-69hGZ8oNuzMLmbz8aOrCvFQ3Dg0=", - "dev": true, - "requires": { - "array-union": "^1.0.1", - "arrify": "^1.0.0", - "glob": "^7.0.3", - "object-assign": "^4.0.1", - "pify": "^2.0.0", - "pinkie-promise": "^2.0.0" - } - }, - "is-path-cwd": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-path-cwd/-/is-path-cwd-1.0.0.tgz", - "integrity": "sha1-0iXsIxMuie3Tj9p2dHLmLmXxEG0=", - "dev": true - }, - "is-path-in-cwd": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-path-in-cwd/-/is-path-in-cwd-1.0.1.tgz", - "integrity": "sha512-FjV1RTW48E7CWM7eE/J2NJvAEEVektecDBVBE5Hh3nM1Jd0kvhHtX68Pr3xsDf857xt3Y4AkwVULK1Vku62aaQ==", - "dev": true, - "requires": { - "is-path-inside": "^1.0.0" - } - }, - "is-path-inside": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-1.0.1.tgz", - "integrity": "sha1-jvW33lBDej/cprToZe96pVy0gDY=", - "dev": true, - "requires": { - "path-is-inside": "^1.0.1" - } - }, - "pify": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", - "integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=", - "dev": true - }, - "semver": { - "version": "5.7.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", - "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", - "dev": true - }, - "source-map": { - "version": "0.5.7", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", - "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", - "dev": true - }, - "source-map-support": { - "version": "0.4.18", - "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.4.18.tgz", - "integrity": "sha512-try0/JqxPLF9nOjvSta7tVondkP5dwgyLDjVoyMDlmjugT2lRZ1OfsrYTkCd2hkDnJTKRbO/Rl3orm8vlsUzbA==", - "dev": true, - "requires": { - "source-map": "^0.5.6" - } - }, - "supports-color": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz", - "integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=", - "dev": true - }, - "webdriver-manager": { - "version": "12.1.7", - "resolved": "https://registry.npmjs.org/webdriver-manager/-/webdriver-manager-12.1.7.tgz", - "integrity": "sha512-XINj6b8CYuUYC93SG3xPkxlyUc3IJbD6Vvo75CVGuG9uzsefDzWQrhz0Lq8vbPxtb4d63CZdYophF8k8Or/YiA==", - "dev": true, - "requires": { - "adm-zip": "^0.4.9", - "chalk": "^1.1.1", - "del": "^2.2.0", - "glob": "^7.0.3", - "ini": "^1.3.4", - "minimist": "1.2.5", - "q": "^1.4.1", - "request": "^2.87.0", - "rimraf": "^2.5.2", - "semver": "^5.3.0", - "xml2js": "^0.4.17" - }, - "dependencies": { - "minimist": { - "version": "1.2.5" - } - } - } - } - }, - "protractor-fail-fast": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/protractor-fail-fast/-/protractor-fail-fast-3.1.0.tgz", - "integrity": "sha512-OjuIFmY7hm5R/Msmioyg3aBevySpmpIgtm2TGUvMEqTzviPk/Fqd1HYmMjIQ+NzFMzrK+93LJa4civDvw1+hEg==", - "dev": true, - "requires": { - "jasmine-fail-fast": "~2.0.0" - } - }, - "protractor-screenshoter-plugin": { - "version": "0.10.3", - "resolved": "https://registry.npmjs.org/protractor-screenshoter-plugin/-/protractor-screenshoter-plugin-0.10.3.tgz", - "integrity": "sha512-OF9kGe1rMxBQY4uXzXQUFT14EB83rz8DlDcxmH5HcOHPBpUhGh+Nwo7+K87w1LoLcTuGdG7Bz+/hGwoGguDfsA==", - "dev": true, - "requires": { - "circular-json": "^0.5.1", - "fs-extra": "^7.0.0", - "klaw-sync": "^6.0.0", - "lodash": "^4.17.11", - "mkdirp": "^0.5.1", - "moment": "^2.20.1", - "q": "^1.5.1", - "screenshoter-report-analyzer": "^0.6", - "uuid": "^3.1.0" - }, - "dependencies": { - "fs-extra": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-7.0.1.tgz", - "integrity": "sha512-YJDaCJZEnBmcbw13fvdAM9AwNOJwOzrE4pqMqBq5nFiEqXUqHwlK4B+3pUw6JNvfSPtX05xFHtYy/1ni01eGCw==", - "dev": true, - "requires": { - "graceful-fs": "^4.1.2", - "jsonfile": "^4.0.0", - "universalify": "^0.1.0" - } - }, - "q": { - "version": "1.5.1", - "resolved": "https://registry.npmjs.org/q/-/q-1.5.1.tgz", - "integrity": "sha1-fjL3W0E4EpHQRhHxvxQQmsAGUdc=", - "dev": true - } - } - }, "proxy-addr": { "version": "2.0.6", "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.6.tgz", @@ -14444,6 +14830,15 @@ "integrity": "sha1-0/wRS6BplaRexok/SEzrHXj19HY=", "dev": true }, + "ps-tree": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/ps-tree/-/ps-tree-1.2.0.tgz", + "integrity": "sha512-0VnamPPYHl4uaU/nSFeZZpR21QAWRz+sRv4iW9+v/GS/J5U5iZB5BNN6J0RMoOvdx2gWM2+ZFMIm58q24e4UYA==", + "dev": true, + "requires": { + "event-stream": "=3.3.4" + } + }, "pseudomap": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/pseudomap/-/pseudomap-1.0.2.tgz", @@ -14507,12 +14902,6 @@ "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.3.2.tgz", "integrity": "sha1-llOgNvt8HuQjQvIyXM7v6jkmxI0=" }, - "q": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/q/-/q-1.4.1.tgz", - "integrity": "sha1-VXBbzZPF82c1MMLCy8DCs63cKG4=", - "dev": true - }, "qs": { "version": "6.5.2", "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.2.tgz", @@ -14553,15 +14942,16 @@ "dev": true, "requires": { "buffer-equal": "0.0.1", - "minimist": "1.2.5", + "minimist": "^1.1.3", "through2": "^2.0.0" - }, - "dependencies": { - "minimist": { - "version": "1.2.5" - } } }, + "ramda": { + "version": "0.26.1", + "resolved": "https://registry.npmjs.org/ramda/-/ramda-0.26.1.tgz", + "integrity": "sha512-hLWjpy7EnsDBb0p+Z3B7rPi3GDeRG5ZtiI33kJhTt+ORCd38AbAIjB/9zRIUoeTbE/AVX5ZkU7m6bznsvrf8eQ==", + "dev": true + }, "randombytes": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", @@ -14618,9 +15008,9 @@ }, "dependencies": { "ajv": { - "version": "6.12.0", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.0.tgz", - "integrity": "sha512-D6gFiFA0RRLyUbvijN74DWAjXSFxWKaWP7mldxkVhyhAV3+SWA9HEJPHQ2c9soIeTFJqcSdFDGFgdqs1iUU2Hw==", + "version": "6.12.2", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.2.tgz", + "integrity": "sha512-k+V+hzjm5q/Mr8ef/1Y9goCmlsK4I6Sm74teeyGvFk1XrOsbsKLjEdrvny42CZ+a8sXbk8KWpY/bDwS+FLL2UQ==", "dev": true, "requires": { "fast-deep-equal": "^3.1.1", @@ -14636,9 +15026,9 @@ "dev": true }, "schema-utils": { - "version": "2.6.5", - "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-2.6.5.tgz", - "integrity": "sha512-5KXuwKziQrTVHh8j/Uxz+QUbxkaLW9X/86NBlx/gnKgtsZA2GIVMUn17qWhRFwF8jdYb3Dig5hRO/W5mZqy6SQ==", + "version": "2.6.6", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-2.6.6.tgz", + "integrity": "sha512-wHutF/WPSbIi9x6ctjGGk2Hvl0VOz5l3EKEuKbjPlB30mKZUzb9A5k9yEXRX3pwyqVLPvpfZZEllaFq/M718hA==", "dev": true, "requires": { "ajv": "^6.12.0", @@ -14695,14 +15085,103 @@ } }, "read-pkg": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-3.0.0.tgz", - "integrity": "sha1-nLxoaXj+5l0WwA4rGcI3/Pbjg4k=", + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-5.2.0.tgz", + "integrity": "sha512-Ug69mNOpfvKDAc2Q8DRpMjjzdtrnv9HcSMX+4VsZxD1aZ6ZzrIE7rlzXBtWTyhULSMKg076AW6WR5iZpD0JiOg==", "dev": true, "requires": { - "load-json-file": "^4.0.0", - "normalize-package-data": "^2.3.2", - "path-type": "^3.0.0" + "@types/normalize-package-data": "^2.4.0", + "normalize-package-data": "^2.5.0", + "parse-json": "^5.0.0", + "type-fest": "^0.6.0" + }, + "dependencies": { + "parse-json": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.0.0.tgz", + "integrity": "sha512-OOY5b7PAEFV0E2Fir1KOkxchnZNCdowAJgQ5NuxjpBKTRP3pQhwkrkxqQjeoKJ+fO7bCpmIZaogI4eZGDMEGOw==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.0.0", + "error-ex": "^1.3.1", + "json-parse-better-errors": "^1.0.1", + "lines-and-columns": "^1.1.6" + } + }, + "type-fest": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.6.0.tgz", + "integrity": "sha512-q+MB8nYR1KDLrgr4G5yemftpMC7/QLqVndBmEEdqzmNj5dcFOO4Oo8qlwZE3ULT3+Zim1F8Kq4cBnikNhlCMlg==", + "dev": true + } + } + }, + "read-pkg-up": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-7.0.1.tgz", + "integrity": "sha512-zK0TB7Xd6JpCLmlLmufqykGE+/TlOePD6qKClNW7hHDKFh/J7/7gCWGR7joEQEW1bKq3a3yUZSObOoWLFQ4ohg==", + "dev": true, + "requires": { + "find-up": "^4.1.0", + "read-pkg": "^5.2.0", + "type-fest": "^0.8.1" + }, + "dependencies": { + "find-up": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", + "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", + "dev": true, + "requires": { + "locate-path": "^5.0.0", + "path-exists": "^4.0.0" + } + }, + "locate-path": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", + "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", + "dev": true, + "requires": { + "p-locate": "^4.1.0" + } + }, + "p-limit": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "dev": true, + "requires": { + "p-try": "^2.0.0" + } + }, + "p-locate": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", + "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", + "dev": true, + "requires": { + "p-limit": "^2.2.0" + } + }, + "p-try": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", + "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", + "dev": true + }, + "path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "dev": true + }, + "type-fest": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.8.1.tgz", + "integrity": "sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA==", + "dev": true + } } }, "readable-stream": { @@ -14733,12 +15212,12 @@ } }, "readdirp": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.3.0.tgz", - "integrity": "sha512-zz0pAkSPOXXm1viEwygWIPSPkcBYjW1xU5j/JBh5t9bGCJwa6f9+BJa6VaB2g+b55yVrmXzqkyLf4xaWYM0IkQ==", + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.4.0.tgz", + "integrity": "sha512-0xe001vZBnJEK+uKcj8qOhyAKPzIT+gStxWr3LCB0DwcXR5NZJ3IaC+yGnHCYzB/S7ov3m3EEbZI2zeNvX+hGQ==", "dev": true, "requires": { - "picomatch": "^2.0.7" + "picomatch": "^2.2.1" } }, "realpath-native": { @@ -14983,9 +15462,9 @@ } }, "p-limit": { - "version": "2.2.2", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.2.2.tgz", - "integrity": "sha512-WGR+xHecKTr7EbUEhyLSh5Dube9JtdiG78ufaeLxTgpudf/20KqyMioIUZJAezlTIi6evxuoUs9YXc11cU+yzQ==", + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", "dev": true, "requires": { "p-try": "^2.0.0" @@ -15084,9 +15563,9 @@ } }, "yargs-parser": { - "version": "18.1.2", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-18.1.2.tgz", - "integrity": "sha512-hlIPNR3IzC1YuL1c2UwwDKpXlNFBqD1Fswwh1khz5+d8Cq/8yc/Mn0i+rQXduu8hcrFKvO7Eryk+09NecTQAAQ==", + "version": "18.1.3", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-18.1.3.tgz", + "integrity": "sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ==", "dev": true, "requires": { "camelcase": "^5.0.0", @@ -15123,6 +15602,15 @@ "uuid": "^3.3.2" } }, + "request-progress": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/request-progress/-/request-progress-3.0.0.tgz", + "integrity": "sha1-TKdUCBx/7GP1BeT6qCWqBs1mnb4=", + "dev": true, + "requires": { + "throttleit": "^1.0.0" + } + }, "request-promise-core": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/request-promise-core/-/request-promise-core-1.1.3.tgz", @@ -15165,9 +15653,9 @@ "integrity": "sha512-LwZrotdHOo12nQuZlHEmtuXdqGoOD0OhaxopaNFxWzInpEgaLWoVuAMbTzixuosCx2nEG58ngzW3vxdWoxIgdg==" }, "resolve": { - "version": "1.15.1", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.15.1.tgz", - "integrity": "sha512-84oo6ZTtoTUpjgNEr5SJyzQhzL72gaRodsSfyxC/AXRvwu0Yse9H8eF9IpGo7b8YetZhlI6v7ZQ6bKBFV/6S7w==", + "version": "1.17.0", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.17.0.tgz", + "integrity": "sha512-ic+7JYiV8Vi2yzQGFWOkiZD5Z9z7O2Zhm9XMaTxdJExKasieFCr+yXZ/WmXsckHiKl12ar0y6XiXDx3m4RHn1w==", "dev": true, "requires": { "path-parse": "^1.0.6" @@ -15277,13 +15765,10 @@ "dev": true }, "run-async": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/run-async/-/run-async-2.4.0.tgz", - "integrity": "sha512-xJTbh/d7Lm7SBhc1tNvTpeCHaEzoyxPrqNlvSdMfBTYwaY++UJFyXUOxAtsRUXjlqOfj8luNaR9vjCh4KeV+pg==", - "dev": true, - "requires": { - "is-promise": "^2.1.0" - } + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/run-async/-/run-async-2.4.1.tgz", + "integrity": "sha512-tvVnVv01b8c1RrA6Ep7JkStj85Guv/YrMcwqYQnwjsAS2cTmmPGBBjAjpCW7RrSodNSoE2/qg9O4bceNvUuDgQ==", + "dev": true }, "run-queue": { "version": "1.0.3", @@ -15336,7 +15821,7 @@ "execa": "^1.0.0", "fb-watchman": "^2.0.0", "micromatch": "^3.1.4", - "minimist": "1.2.5", + "minimist": "^1.1.1", "walker": "~1.0.5" }, "dependencies": { @@ -15387,9 +15872,6 @@ "pump": "^3.0.0" } }, - "minimist": { - "version": "1.2.5" - }, "normalize-path": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-2.1.1.tgz", @@ -15437,15 +15919,6 @@ } } }, - "saucelabs": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/saucelabs/-/saucelabs-1.5.0.tgz", - "integrity": "sha512-jlX3FGdWvYf4Q3LFfFWS1QvPg3IGCGWxIc8QBFdPTbpTJnt/v17FHXYVAn7C8sHf1yUXo2c7yIM0isDryfYtHQ==", - "dev": true, - "requires": { - "https-proxy-agent": "^2.2.1" - } - }, "sax": { "version": "0.5.8", "resolved": "https://registry.npmjs.org/sax/-/sax-0.5.8.tgz", @@ -15487,41 +15960,12 @@ "get-assigned-identifiers": "^1.1.0" } }, - "screenshoter-report-analyzer": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/screenshoter-report-analyzer/-/screenshoter-report-analyzer-0.6.0.tgz", - "integrity": "sha1-Cm+I1fXRrBa2z3Ji7/ujH+5I7RI=", - "dev": true - }, "select-hose": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/select-hose/-/select-hose-2.0.0.tgz", "integrity": "sha1-Yl2GWPhlr0Psliv8N2o3NZpJlMo=", "dev": true }, - "selenium-webdriver": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/selenium-webdriver/-/selenium-webdriver-3.6.0.tgz", - "integrity": "sha512-WH7Aldse+2P5bbFBO4Gle/nuQOdVwpHMTL6raL3uuBj/vPG07k6uzt3aiahu352ONBr5xXh0hDlM3LhtXPOC4Q==", - "dev": true, - "requires": { - "jszip": "^3.1.3", - "rimraf": "^2.5.4", - "tmp": "0.0.30", - "xml2js": "^0.4.17" - }, - "dependencies": { - "tmp": { - "version": "0.0.30", - "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.30.tgz", - "integrity": "sha1-ckGdSovn1s51FI/YsyTlk6cRwu0=", - "dev": true, - "requires": { - "os-tmpdir": "~1.0.1" - } - } - } - }, "selfsigned": { "version": "1.10.7", "resolved": "https://registry.npmjs.org/selfsigned/-/selfsigned-1.10.7.tgz", @@ -15696,12 +16140,6 @@ "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", "integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc=" }, - "set-immediate-shim": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/set-immediate-shim/-/set-immediate-shim-1.0.1.tgz", - "integrity": "sha1-SysbJ+uAip+NzEgaWOXlb1mfP2E=", - "dev": true - }, "set-value": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/set-value/-/set-value-2.0.1.tgz", @@ -15827,6 +16265,12 @@ "integrity": "sha1-xB8vbDn8FtHNF61LXYlhFK5HDVU=", "dev": true }, + "slice-ansi": { + "version": "0.0.4", + "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-0.0.4.tgz", + "integrity": "sha1-7b+JA/ZvfOL46v1s7tZeJkyDGzU=", + "dev": true + }, "smart-buffer": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/smart-buffer/-/smart-buffer-4.1.0.tgz", @@ -16121,9 +16565,9 @@ } }, "spdx-exceptions": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/spdx-exceptions/-/spdx-exceptions-2.2.0.tgz", - "integrity": "sha512-2XQACfElKi9SlVb1CYadKDXvoajPgBVPn/gOQLrTvHdElaVhr7ZEbqJaRnJLVNeaI4cMEAgVCeBMKF6MWRDCRA==", + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/spdx-exceptions/-/spdx-exceptions-2.3.0.tgz", + "integrity": "sha512-/tTrYOC7PPI1nUAgx34hUpqXuyJG+DTHJTnIULG4rDygi4xu/tfgmq1e1cIRwRzwZgo4NLySi+ricLkZkw4i5A==", "dev": true }, "spdx-expression-parse": { @@ -16143,9 +16587,9 @@ "dev": true }, "spdy": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/spdy/-/spdy-4.0.1.tgz", - "integrity": "sha512-HeZS3PBdMA+sZSu0qwpCxl3DeALD5ASx8pAX0jZdKXSpPWbQ6SYGnlg3BBmYLx5LtiZrmkAZfErCm2oECBcioA==", + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/spdy/-/spdy-4.0.2.tgz", + "integrity": "sha512-r46gZQZQV+Kl9oItvl1JZZqJKGr+oEkB08A6BzkiR7593/7IbtuncXHd2YoYeTsG4157ZssMu9KYvUHLcjcDoA==", "dev": true, "requires": { "debug": "^4.1.0", @@ -16247,37 +16691,121 @@ "integrity": "sha512-MTX+MeG5U994cazkjd/9KNAapsHnibjMLnfXodlkXw76JEea0UiNzrqidzo1emMwk7w5Qhc9jd4Bn9TBb1MFwA==", "dev": true }, - "static-eval": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/static-eval/-/static-eval-2.0.5.tgz", - "integrity": "sha512-nNbV6LbGtMBgv7e9LFkt5JV8RVlRsyJrphfAt9tOtBBW/SfnzZDf2KnS72an8e434A+9e/BmJuTxeGPvrAK7KA==", + "start-server-and-test": { + "version": "1.11.0", + "resolved": "https://registry.npmjs.org/start-server-and-test/-/start-server-and-test-1.11.0.tgz", + "integrity": "sha512-FhkJFYL/lvbd0tKWvbxWNWjtFtq3Zpa09QDjA8EUH88AsgNL4hkAAKYNmbac+fFM8/GIZoJ1Mj4mm3SMI0X1bA==", "dev": true, "requires": { - "escodegen": "^1.11.1" + "bluebird": "3.7.2", + "check-more-types": "2.24.0", + "debug": "4.1.1", + "execa": "3.4.0", + "lazy-ass": "1.6.0", + "ps-tree": "1.2.0", + "wait-on": "4.0.0" }, "dependencies": { - "escodegen": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/escodegen/-/escodegen-1.14.1.tgz", - "integrity": "sha512-Bmt7NcRySdIfNPfU2ZoXDrrXsG9ZjvDxcAlMfDUgRBjLOWTuIACXPBFJH7Z+cLb40JeQco5toikyc9t9P8E9SQ==", + "cross-spawn": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.2.tgz", + "integrity": "sha512-PD6G8QG3S4FK/XCGFbEQrDqO2AnMMsy0meR7lerlIOHAAbkuavGU/pOqprrlvfTNjvowivTeBsjebAL0NSoMxw==", "dev": true, "requires": { - "esprima": "^4.0.1", - "estraverse": "^4.2.0", - "esutils": "^2.0.2", - "optionator": "^0.8.1", - "source-map": "~0.6.1" + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" } }, - "source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "execa": { + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/execa/-/execa-3.4.0.tgz", + "integrity": "sha512-r9vdGQk4bmCuK1yKQu1KTwcT2zwfWdbdaXfCtAh+5nU/4fSX+JAb7vZGvI5naJrQlvONrEB20jeruESI69530g==", "dev": true, - "optional": true + "requires": { + "cross-spawn": "^7.0.0", + "get-stream": "^5.0.0", + "human-signals": "^1.1.1", + "is-stream": "^2.0.0", + "merge-stream": "^2.0.0", + "npm-run-path": "^4.0.0", + "onetime": "^5.1.0", + "p-finally": "^2.0.0", + "signal-exit": "^3.0.2", + "strip-final-newline": "^2.0.0" + } + }, + "get-stream": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-5.1.0.tgz", + "integrity": "sha512-EXr1FOzrzTfGeL0gQdeFEvOMm2mzMOglyiOXSTpPC+iAjAKftbr3jpCMWynogwYnM+eSj9sHGc6wjIcDvYiygw==", + "dev": true, + "requires": { + "pump": "^3.0.0" + } + }, + "is-stream": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.0.tgz", + "integrity": "sha512-XCoy+WlUr7d1+Z8GgSuXmpuUFC9fOhRXglJMx+dwLKTkL44Cjd4W1Z5P+BQZpr+cR93aGP4S/s7Ftw6Nd/kiEw==", + "dev": true + }, + "npm-run-path": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz", + "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==", + "dev": true, + "requires": { + "path-key": "^3.0.0" + } + }, + "p-finally": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/p-finally/-/p-finally-2.0.1.tgz", + "integrity": "sha512-vpm09aKwq6H9phqRQzecoDpD8TmVyGw70qmWlyq5onxY7tqyTTFVvxMykxQSQKILBSFlbXpypIw2T1Ml7+DDtw==", + "dev": true + }, + "path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true + }, + "shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, + "requires": { + "shebang-regex": "^3.0.0" + } + }, + "shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true + }, + "which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "requires": { + "isexe": "^2.0.0" + } } } }, + "static-eval": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/static-eval/-/static-eval-2.0.5.tgz", + "integrity": "sha512-nNbV6LbGtMBgv7e9LFkt5JV8RVlRsyJrphfAt9tOtBBW/SfnzZDf2KnS72an8e434A+9e/BmJuTxeGPvrAK7KA==", + "dev": true, + "requires": { + "escodegen": "^1.11.1" + } + }, "static-extend": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/static-extend/-/static-extend-0.1.2.tgz", @@ -16300,34 +16828,34 @@ } }, "static-module": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/static-module/-/static-module-3.0.3.tgz", - "integrity": "sha512-RDaMYaI5o/ym0GkCqL/PlD1Pn216omp8fY81okxZ6f6JQxWW5tptOw9reXoZX85yt/scYvbWIt6uoszeyf+/MQ==", + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/static-module/-/static-module-3.0.4.tgz", + "integrity": "sha512-gb0v0rrgpBkifXCa3yZXxqVmXDVE+ETXj6YlC/jt5VzOnGXR2C15+++eXuMDUYsePnbhf+lwW0pE1UXyOLtGCw==", "dev": true, "requires": { "acorn-node": "^1.3.0", "concat-stream": "~1.6.0", "convert-source-map": "^1.5.1", "duplexer2": "~0.1.4", - "escodegen": "~1.9.0", + "escodegen": "^1.11.1", "has": "^1.0.1", - "magic-string": "^0.22.4", + "magic-string": "0.25.1", "merge-source-map": "1.0.4", - "object-inspect": "~1.4.0", + "object-inspect": "^1.6.0", "readable-stream": "~2.3.3", "scope-analyzer": "^2.0.1", "shallow-copy": "~0.0.1", - "static-eval": "^2.0.2", + "static-eval": "^2.0.5", "through2": "~2.0.3" }, "dependencies": { "magic-string": { - "version": "0.22.5", - "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.22.5.tgz", - "integrity": "sha512-oreip9rJZkzvA8Qzk9HFs8fZGF/u7H/gtrE8EN6RjKJ9kh2HlC+yQ2QezifqTZfGyiuAV0dRv5a+y/8gBb1m9w==", + "version": "0.25.1", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.25.1.tgz", + "integrity": "sha512-sCuTz6pYom8Rlt4ISPFn6wuFodbKMIHUMv4Qko9P17dpxb7s52KJTmRuZZqHdGmLCK9AOcDare039nRIcfdkEg==", "dev": true, "requires": { - "vlq": "^0.2.2" + "sourcemap-codec": "^1.4.1" } }, "merge-source-map": { @@ -16339,12 +16867,6 @@ "source-map": "^0.5.6" } }, - "object-inspect": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.4.1.tgz", - "integrity": "sha512-wqdhLpfCUbEsoEwl3FXwGyv8ief1k/1aUdIPCqVnupM6e8l63BEJdiF/0swtn04/8p05tG/T0FrpTlfwvljOdw==", - "dev": true - }, "source-map": { "version": "0.5.7", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", @@ -16486,9 +17008,9 @@ } }, "string.prototype.trimend": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.0.tgz", - "integrity": "sha512-EEJnGqa/xNfIg05SxiPSqRS7S9qwDhYts1TSLR1BQfYUfPe1stofgGKvwERK9+9yf+PpfBMlpBaCHucXGPQfUA==", + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.1.tgz", + "integrity": "sha512-LRPxFUaTtpqYsTeNKaFOw3R4bxIzWOnbQ837QfBylo8jIxtcbK/A/sMV7Q+OAV/vWo+7s25pOE10KYSjaSO06g==", "dev": true, "requires": { "define-properties": "^1.1.3", @@ -16518,9 +17040,9 @@ } }, "string.prototype.trimstart": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.0.tgz", - "integrity": "sha512-iCP8g01NFYiiBOnwG1Xc3WZLyoo+RuBymwIlWncShXDDJYWN6DbnM3odslBJdgCdRlq94B5s63NWAZlcn2CS4w==", + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.1.tgz", + "integrity": "sha512-XxZn+QpvrBI1FOcg6dIpxUPgWCPuNXvMD72aaRaUQv1eD4e/Qy8i/hFTe0BUmD60p/QA6bh1avmuPTfNjqVWRw==", "dev": true, "requires": { "define-properties": "^1.1.3", @@ -16572,9 +17094,9 @@ }, "dependencies": { "ajv": { - "version": "6.12.0", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.0.tgz", - "integrity": "sha512-D6gFiFA0RRLyUbvijN74DWAjXSFxWKaWP7mldxkVhyhAV3+SWA9HEJPHQ2c9soIeTFJqcSdFDGFgdqs1iUU2Hw==", + "version": "6.12.2", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.2.tgz", + "integrity": "sha512-k+V+hzjm5q/Mr8ef/1Y9goCmlsK4I6Sm74teeyGvFk1XrOsbsKLjEdrvny42CZ+a8sXbk8KWpY/bDwS+FLL2UQ==", "dev": true, "requires": { "fast-deep-equal": "^3.1.1", @@ -16590,9 +17112,9 @@ "dev": true }, "schema-utils": { - "version": "2.6.5", - "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-2.6.5.tgz", - "integrity": "sha512-5KXuwKziQrTVHh8j/Uxz+QUbxkaLW9X/86NBlx/gnKgtsZA2GIVMUn17qWhRFwF8jdYb3Dig5hRO/W5mZqy6SQ==", + "version": "2.6.6", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-2.6.6.tgz", + "integrity": "sha512-wHutF/WPSbIi9x6ctjGGk2Hvl0VOz5l3EKEuKbjPlB30mKZUzb9A5k9yEXRX3pwyqVLPvpfZZEllaFq/M718hA==", "dev": true, "requires": { "ajv": "^6.12.0", @@ -16840,6 +17362,12 @@ "integrity": "sha512-fcwX4mndzpLQKBS1DVYhGAcYaYt7vsHNIvQV+WXMvnow5cgjPphq5CaayLaGsjRdSCKZFNGt7/GYAuXaNOiYCA==", "dev": true }, + "throttleit": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/throttleit/-/throttleit-1.0.0.tgz", + "integrity": "sha1-nnhYNtr0Z0MUWlmEtiaNgoUorGw=", + "dev": true + }, "through": { "version": "2.3.8", "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", @@ -16999,16 +17527,11 @@ "dev": true, "requires": { "colors": "^1.0.3", - "minimist": "1.2.5", + "minimist": "^1.2.0", "prompts": "^2.0.4", "request": "^2.88.0", "request-promise-native": "^1.0.7", "xliff": "^4.2.0" - }, - "dependencies": { - "minimist": { - "version": "1.2.5" - } } }, "traverse": { @@ -17024,9 +17547,9 @@ "dev": true }, "ts-jest": { - "version": "25.3.0", - "resolved": "https://registry.npmjs.org/ts-jest/-/ts-jest-25.3.0.tgz", - "integrity": "sha512-qH/uhaC+AFDU9JfAueSr0epIFJkGMvUPog4FxSEVAtPOur1Oni5WBJMiQIkfHvc7PviVRsnlVLLY2I6221CQew==", + "version": "25.4.0", + "resolved": "https://registry.npmjs.org/ts-jest/-/ts-jest-25.4.0.tgz", + "integrity": "sha512-+0ZrksdaquxGUBwSdTIcdX7VXdwLIlSRsyjivVA9gcO+Cvr6ByqDhu/mi5+HCcb6cMkiQp5xZ8qRO7/eCqLeyw==", "dev": true, "requires": { "bs-logger": "0.x", @@ -17035,10 +17558,11 @@ "json5": "2.x", "lodash.memoize": "4.x", "make-error": "1.x", + "micromatch": "4.x", "mkdirp": "1.x", "resolve": "1.x", "semver": "6.x", - "yargs-parser": "^18.1.1" + "yargs-parser": "18.x" }, "dependencies": { "camelcase": { @@ -17048,29 +17572,34 @@ "dev": true }, "json5": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/json5/-/json5-2.1.2.tgz", - "integrity": "sha512-MoUOQ4WdiN3yxhm7NEVJSJrieAo5hNSLQ5sj05OTRHPL9HOBy8u4Bu88jsC1jvqAdN+E1bJmsUcZH+1HQxliqQ==", + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.1.3.tgz", + "integrity": "sha512-KXPvOm8K9IJKFM0bmdn8QXh7udDh1g/giieX0NLCaMnb4hEiVFqnop2ImTXCc5e0/oHz3LTqmHGtExn5hfMkOA==", "dev": true, "requires": { - "minimist": "1.2.5" - }, - "dependencies": { - "minimist": { - "version": "1.2.5" - } + "minimist": "^1.2.5" + } + }, + "micromatch": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.2.tgz", + "integrity": "sha512-y7FpHSbMUMoyPbYUSzO6PaZ6FyRnQOpHuKwbo1G+Knck95XVU4QAiKdGEnj5wwoS7PlOgthX/09u5iFJ+aYf5Q==", + "dev": true, + "requires": { + "braces": "^3.0.1", + "picomatch": "^2.0.5" } }, "mkdirp": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.3.tgz", - "integrity": "sha512-6uCP4Qc0sWsgMLy1EOqqS/3rjDHOEnsStVr/4vtAIK2Y5i2kA7lFFejYrpIyiN9w0pYf4ckeCYT9f1r1P9KX5g==", + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", + "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", "dev": true }, "yargs-parser": { - "version": "18.1.2", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-18.1.2.tgz", - "integrity": "sha512-hlIPNR3IzC1YuL1c2UwwDKpXlNFBqD1Fswwh1khz5+d8Cq/8yc/Mn0i+rQXduu8hcrFKvO7Eryk+09NecTQAAQ==", + "version": "18.1.3", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-18.1.3.tgz", + "integrity": "sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ==", "dev": true, "requires": { "camelcase": "^5.0.0", @@ -17283,23 +17812,13 @@ "dev": true }, "uglify-js": { - "version": "3.8.1", - "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.8.1.tgz", - "integrity": "sha512-W7KxyzeaQmZvUFbGj4+YFshhVrMBGSg2IbcYAjGWGvx8DHvJMclbTDMpffdxFUGPBHjIytk7KJUR/KUXstUGDw==", + "version": "3.9.1", + "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.9.1.tgz", + "integrity": "sha512-JUPoL1jHsc9fOjVFHdQIhqEEJsQvfKDjlubcCilu8U26uZ73qOg8VsN8O1jbuei44ZPlwL7kmbAdM4tzaUvqnA==", "dev": true, "optional": true, "requires": { - "commander": "~2.20.3", - "source-map": "~0.6.1" - }, - "dependencies": { - "source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true, - "optional": true - } + "commander": "~2.20.3" } }, "unc-path-regex": { @@ -17492,6 +18011,12 @@ } } }, + "untildify": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/untildify/-/untildify-4.0.0.tgz", + "integrity": "sha512-KK8xQ1mkzZeg9inewmFVDNkg3l5LUhoq9kN6iWYB/CC9YMG8HA+c1Q8HwDe6dEX7kErrEVNVBO3fWsVq5iDgtw==", + "dev": true + }, "upath": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/upath/-/upath-1.2.0.tgz", @@ -17643,12 +18168,6 @@ "integrity": "sha512-W+1+N/hdzLpQZEcvz79n2IgUE9pfx6JLdHh3Kh8RGvLL8P1LdJVQmi2OsDcLdY4QVID4OUy+FPelyerX0nJxIQ==", "dev": true }, - "vlq": { - "version": "0.2.3", - "resolved": "https://registry.npmjs.org/vlq/-/vlq-0.2.3.tgz", - "integrity": "sha512-DRibZL6DsNhIgYQ+wNdWDL2SL3bKPlVrRiBqV5yuMm++op8W4kGFtaQfCs4KEJn0wBZcHVHJ3eoywX8983k1ow==", - "dev": true - }, "vm-browserify": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/vm-browserify/-/vm-browserify-1.1.2.tgz", @@ -17675,6 +18194,20 @@ "xml-name-validator": "^3.0.0" } }, + "wait-on": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/wait-on/-/wait-on-4.0.0.tgz", + "integrity": "sha512-QrW3J8LzS5ADPfD9Rx5S6KJck66xkqyiFKQs9jmUTkIhiEOmkzU7WRZc+MjsnmkrgjitS2xQ4bb13hnlQnKBUQ==", + "dev": true, + "requires": { + "@hapi/joi": "^16.1.8", + "lodash": "^4.17.15", + "minimist": "^1.2.0", + "request": "^2.88.0", + "request-promise-native": "^1.0.8", + "rxjs": "^6.5.4" + } + }, "walker": { "version": "1.0.7", "resolved": "https://registry.npmjs.org/walker/-/walker-1.0.7.tgz", @@ -18010,9 +18543,10 @@ } }, "minimist": { + "version": "1.2.5", + "bundled": true, "dev": true, - "optional": true, - "version": "1.2.5" + "optional": true }, "minipass": { "version": "2.9.0", @@ -18039,12 +18573,7 @@ "dev": true, "optional": true, "requires": { - "minimist": "1.2.5" - }, - "dependencies": { - "minimist": { - "version": "1.2.5" - } + "minimist": "^1.2.5" } }, "ms": { @@ -18193,13 +18722,8 @@ "requires": { "deep-extend": "^0.6.0", "ini": "~1.3.0", - "minimist": "1.2.5", + "minimist": "^1.2.0", "strip-json-comments": "~2.0.1" - }, - "dependencies": { - "minimist": { - "version": "1.2.5" - } } }, "readable-stream": { @@ -18401,16 +18925,6 @@ "minimalistic-assert": "^1.0.0" } }, - "webdriver-js-extender": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/webdriver-js-extender/-/webdriver-js-extender-2.1.0.tgz", - "integrity": "sha512-lcUKrjbBfCK6MNsh7xaY2UAUmZwe+/ib03AjVOpFobX4O7+83BUveSrLfU0Qsyb1DaKJdQRbuU+kM9aZ6QUhiQ==", - "dev": true, - "requires": { - "@types/selenium-webdriver": "^3.0.0", - "selenium-webdriver": "^3.0.1" - } - }, "webidl-conversions": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-4.0.2.tgz", @@ -18931,9 +19445,10 @@ } }, "minimist": { + "version": "1.2.5", + "bundled": true, "dev": true, - "optional": true, - "version": "1.2.5" + "optional": true }, "minipass": { "version": "2.9.0", @@ -18960,12 +19475,7 @@ "dev": true, "optional": true, "requires": { - "minimist": "1.2.5" - }, - "dependencies": { - "minimist": { - "version": "1.2.5" - } + "minimist": "^1.2.5" } }, "ms": { @@ -19114,13 +19624,8 @@ "requires": { "deep-extend": "^0.6.0", "ini": "~1.3.0", - "minimist": "1.2.5", + "minimist": "^1.2.0", "strip-json-comments": "~2.0.1" - }, - "dependencies": { - "minimist": { - "version": "1.2.5" - } } }, "readable-stream": { @@ -19356,9 +19861,9 @@ } }, "p-limit": { - "version": "2.2.2", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.2.2.tgz", - "integrity": "sha512-WGR+xHecKTr7EbUEhyLSh5Dube9JtdiG78ufaeLxTgpudf/20KqyMioIUZJAezlTIi6evxuoUs9YXc11cU+yzQ==", + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", "dev": true, "requires": { "p-try": "^2.0.0" @@ -19550,9 +20055,9 @@ "integrity": "sha1-2e8H3Od7mQK4o6j6SzHD4/fm6Ho=" }, "windows-release": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/windows-release/-/windows-release-3.2.0.tgz", - "integrity": "sha512-QTlz2hKLrdqukrsapKsINzqMgOUpQW268eJ0OaOpJN32h272waxR9fkB9VoWRtK7uKHG5EHJcTXQBD8XZVJkFA==", + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/windows-release/-/windows-release-3.3.0.tgz", + "integrity": "sha512-2HetyTg1Y+R+rUgrKeUEhAG/ZuOmTrI1NBb3ZyAGQMYmOJjBBPe4MTodghRkmLJZHwkuPi02anbeGP+Zf401LQ==", "dev": true, "requires": { "execa": "^1.0.0" @@ -19610,9 +20115,9 @@ "dev": true }, "wordwrap": { - "version": "0.0.3", - "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-0.0.3.tgz", - "integrity": "sha1-o9XabNXAvAAI03I0u68b7WMFkQc=", + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-1.0.0.tgz", + "integrity": "sha1-J1hIEIkUVqQXHI0CJkQa3pDLyus=", "dev": true }, "worker-farm": { @@ -19712,30 +20217,6 @@ "integrity": "sha512-A5CUptxDsvxKJEU3yO6DuWBSJz/qizqzJKOMIfUJHETbBw/sFaDxgd6fxm1ewUaM0jZ444Fc5vC5ROYurg/4Pw==", "dev": true }, - "xml2js": { - "version": "0.4.23", - "resolved": "https://registry.npmjs.org/xml2js/-/xml2js-0.4.23.tgz", - "integrity": "sha512-ySPiMjM0+pLDftHgXY4By0uswI3SPKLDw/i3UXbnO8M/p28zqexCUoPmQFrYD+/1BzhGJSs2i1ERWKJAtiLrug==", - "dev": true, - "requires": { - "sax": ">=0.6.0", - "xmlbuilder": "~11.0.0" - }, - "dependencies": { - "sax": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/sax/-/sax-1.2.4.tgz", - "integrity": "sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw==", - "dev": true - } - } - }, - "xmlbuilder": { - "version": "11.0.1", - "resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-11.0.1.tgz", - "integrity": "sha512-fDlsI/kFEx7gLvbecc0/ohLG50fugQp8ryHzMTuW9vSa1GJ0XYWKnhsUx7oie3G98+r56aTQIUB4kht42R3JvA==", - "dev": true - }, "xmlchars": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/xmlchars/-/xmlchars-2.2.0.tgz", @@ -19785,6 +20266,16 @@ "camelcase": "^4.1.0" } }, + "yauzl": { + "version": "2.10.0", + "resolved": "https://registry.npmjs.org/yauzl/-/yauzl-2.10.0.tgz", + "integrity": "sha1-x+sXyT4RLLEIb6bY5R+wZnt5pfk=", + "dev": true, + "requires": { + "buffer-crc32": "~0.2.3", + "fd-slicer": "~1.1.0" + } + }, "yn": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz", diff --git a/src/pybind/mgr/dashboard/frontend/package.json b/src/pybind/mgr/dashboard/frontend/package.json index 282d98e85606..8aeece9c5d6d 100644 --- a/src/pybind/mgr/dashboard/frontend/package.json +++ b/src/pybind/mgr/dashboard/frontend/package.json @@ -33,17 +33,16 @@ "test": "npm run test:config && jest --watch", "test:ci": "npm run test:config && JEST_SILENT_REPORTER_DOTS=true jest --coverage --reporters jest-silent-reporter", "test:config": "if [ ! -e 'src/unit-test-configuration.ts' ]; then cp 'src/unit-test-configuration.ts.sample' 'src/unit-test-configuration.ts'; fi", - "e2e": "npm run env_build && npm run e2e:update && ng e2e --webdriverUpdate=false", - "e2e:ci": "npm run env_build && npm run e2e:update && ng e2e --dev-server-target --webdriverUpdate=false", - "e2e:update": "npx webdriver-manager update --gecko=false --versions.chrome=$(google-chrome --version | awk '{ print $3 }')", + "e2e": "start-test 4200 'cypress open'", + "e2e:ci": "start-test 4200 'cypress run -b chrome --headless'", "lint:tslint": "ng lint", - "lint:prettier": "prettier --list-different \"{src,e2e}/**/*.{ts,scss}\"", + "lint:prettier": "prettier --list-different \"{src,cypress}/**/*.{ts,scss}\"", "lint:html": "htmllint src/app/**/*.html && html-linter --config html-linter.config.json", - "lint:tsc": "npm run test:config && tsc -p src/tsconfig.app.json --noEmit && tsc -p tsconfig.spec.json --noEmit && tsc -p e2e/tsconfig.e2e.json --noEmit", + "lint:tsc": "npm run test:config && tsc -p src/tsconfig.app.json --noEmit && tsc -p tsconfig.spec.json --noEmit && tsc -p cypress/tsconfig.json --noEmit", "lint": "npm run lint:tsc && npm run lint:tslint && npm run lint:prettier && npm run lint:html", - "fix:prettier": "prettier --write \"{src,e2e}/**/*.{ts,scss}\"", + "fix:prettier": "prettier --write \"{src,cypress}/**/*.{ts,scss}\"", "fix:tslint": "npm run lint:tslint -- --fix", - "fixmod": "pretty-quick --pattern \"{src,e2e}/**/*.{ts,scss}\" --branch HEAD", + "fixmod": "pretty-quick --pattern \"{src,cypress}/**/*.{ts,scss}\" --branch HEAD", "fix": "npm run fix:tslint; npm run fix:prettier", "fix:audit": "npx npm-force-resolutions", "compodoc": "compodoc", @@ -116,17 +115,14 @@ "@angular/compiler-cli": "8.2.14", "@angular/language-service": "8.2.14", "@compodoc/compodoc": "1.1.11", - "@types/jasmine": "3.5.10", - "@types/jasminewd2": "2.0.8", "@types/jest": "25.1.4", "@types/lodash": "4.14.149", "@types/node": "12.12.34", "@types/simplebar": "5.1.1", "codelyzer": "5.2.2", + "cypress": "4.4.0", "html-linter": "1.1.1", "htmllint-cli": "0.0.7", - "jasmine-core": "3.5.0", - "jasmine-spec-reporter": "5.0.1", "jest": "25.2.4", "jest-canvas-mock": "2.2.0", "jest-preset-angular": "8.1.3", @@ -137,10 +133,8 @@ "npm-run-all": "4.1.5", "prettier": "2.0.2", "pretty-quick": "2.0.1", - "protractor": "5.4.3", - "protractor-fail-fast": "3.1.0", - "protractor-screenshoter-plugin": "0.10.3", "replace-in-file": "5.0.2", + "start-server-and-test": "1.11.0", "transifex-i18ntool": "1.1.0", "ts-node": "8.8.1", "tslint": "6.1.0", @@ -148,7 +142,6 @@ }, "resolutions": { "mem": "4.3.0", - "minimist": "1.2.5", "fsevents": "2.1.2" } } diff --git a/src/pybind/mgr/dashboard/frontend/protractor.conf.js b/src/pybind/mgr/dashboard/frontend/protractor.conf.js deleted file mode 100644 index c7edd9186da5..000000000000 --- a/src/pybind/mgr/dashboard/frontend/protractor.conf.js +++ /dev/null @@ -1,82 +0,0 @@ -// Protractor configuration file, see link for more information -// https://github.com/angular/protractor/blob/master/lib/config.ts - -const { SpecReporter } = require('jasmine-spec-reporter'); -let failFast = require('protractor-fail-fast'); - -const config = { - SELENIUM_PROMISE_MANAGER: false, - allScriptsTimeout: 11000, - implicitWaitTimeout: 9000, - suites: { - block: './e2e/block/*.e2e-spec.ts', - cluster: './e2e/cluster/*.e2e-spec.ts', - filesystems: './e2e/filesystems/*.e2e-spec.ts', - nfs: './e2e/nfs/*.e2e-spec.ts', - pools: './e2e/pools/*.e2e-spec.ts', - rgw: './e2e/rgw/*.e2e-spec.ts', - ui: './e2e/ui/*.e2e-spec.ts' - }, - capabilities: { - browserName: 'chrome', - chromeOptions: { - args: ['--no-sandbox', '--headless', '--window-size=1920x1080'] - }, - acceptInsecureCerts: true - }, - directConnect: true, - baseUrl: process.env.BASE_URL || 'http://localhost:4200/', - framework: 'jasmine', - jasmineNodeOpts: { - showColors: true, - defaultTimeoutInterval: 300000, - print: function () {} - }, - params: { - login: { - user: process.env.E2E_LOGIN_USER || 'admin', - password: process.env.E2E_LOGIN_PWD || 'admin' - } - }, - - plugins: [ - { - package: 'protractor-screenshoter-plugin', - screenshotPath: '.protractor-report', - screenshotOnExpect: 'failure', - screenshotOnSpec: 'none', - withLogs: true, - writeReportFreq: 'asap', - imageToAscii: 'none', - clearFoldersBeforeTest: true - }, - failFast.init() - ], - afterLaunch: function () { - failFast.clean(); - } -}; - -config.onPrepare = async () => { - await browser.manage().timeouts().implicitlyWait(config.implicitWaitTimeout); - - require('ts-node').register({ - project: 'e2e/tsconfig.e2e.json' - }); - jasmine - .getEnv() - .addReporter( - new SpecReporter({ spec: { displayStacktrace: 'pretty', displayDuration: true } }) - ); - - await browser.get('/#/login'); - - await browser.driver.findElement(by.name('username')).clear(); - await browser.driver.findElement(by.name('username')).sendKeys(browser.params.login.user); - await browser.driver.findElement(by.name('password')).clear(); - await browser.driver.findElement(by.name('password')).sendKeys(browser.params.login.password); - - await browser.driver.findElement(by.css('input[type="submit"]')).click(); -}; - -exports.config = config; diff --git a/src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/monitor/monitor.component.ts b/src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/monitor/monitor.component.ts index c9a95592224a..441351b729a4 100644 --- a/src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/monitor/monitor.component.ts +++ b/src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/monitor/monitor.component.ts @@ -40,8 +40,7 @@ export class MonitorComponent { return lastValueA > lastValueB ? 1 : -1; } } - ], - data: [] + ] }; this.notInQuorum = { @@ -49,8 +48,7 @@ export class MonitorComponent { { prop: 'name', name: this.i18n('Name'), cellTransformation: CellTemplate.routerLink }, { prop: 'rank', name: this.i18n('Rank') }, { prop: 'public_addr', name: this.i18n('Public Address') } - ], - data: [] + ] }; } diff --git a/src/pybind/mgr/dashboard/frontend/src/app/ceph/pool/pool-list/pool-list.component.ts b/src/pybind/mgr/dashboard/frontend/src/app/ceph/pool/pool-list/pool-list.component.ts index c0cd8bb5f301..61fa3585183f 100644 --- a/src/pybind/mgr/dashboard/frontend/src/app/ceph/pool/pool-list/pool-list.component.ts +++ b/src/pybind/mgr/dashboard/frontend/src/app/ceph/pool/pool-list/pool-list.component.ts @@ -48,7 +48,7 @@ export class PoolListComponent extends ListWithDetails implements OnInit { @ViewChild('poolConfigurationSourceTpl', { static: false }) poolConfigurationSourceTpl: TemplateRef; - pools: Pool[] = []; + pools: Pool[]; columns: CdTableColumn[]; selection = new CdTableSelection(); modalRef: BsModalRef; diff --git a/src/pybind/mgr/dashboard/frontend/tsconfig.json b/src/pybind/mgr/dashboard/frontend/tsconfig.json index b72faa4b0f21..ccdf3678dcc0 100644 --- a/src/pybind/mgr/dashboard/frontend/tsconfig.json +++ b/src/pybind/mgr/dashboard/frontend/tsconfig.json @@ -28,9 +28,9 @@ "allowJs": true }, "exclude": [ - ".protractor-report", "coverage", "dist", - "node_modules" + "node_modules", + "cypress" ] } diff --git a/src/pybind/mgr/dashboard/run-frontend-e2e-tests.sh b/src/pybind/mgr/dashboard/run-frontend-e2e-tests.sh index 043e4e00a3cf..56022a53e278 100755 --- a/src/pybind/mgr/dashboard/run-frontend-e2e-tests.sh +++ b/src/pybind/mgr/dashboard/run-frontend-e2e-tests.sh @@ -2,6 +2,25 @@ set -e +start_ceph() { + cd $FULL_PATH_BUILD_DIR + + MGR=2 RGW=1 ../src/vstart.sh -n -d + sleep 10 + + # Create an Object Gateway User + ./bin/radosgw-admin user create --uid=dev --display-name=Developer --system + # Set the user-id + ./bin/ceph dashboard set-rgw-api-user-id dev + # Obtain and set access and secret key for the previously created user. $() is safer than backticks `..` + ./bin/ceph dashboard set-rgw-api-access-key $(./bin/radosgw-admin user info --uid=dev | jq -r .keys[0].access_key) + ./bin/ceph dashboard set-rgw-api-secret-key $(./bin/radosgw-admin user info --uid=dev | jq -r .keys[0].secret_key) + # Set SSL verify to False + ./bin/ceph dashboard set-rgw-api-ssl-verify False + + CYPRESS_BASE_URL=$(./bin/ceph mgr services | jq -r .dashboard) +} + stop() { if [ "$REMOTE" == "false" ]; then cd ${FULL_PATH_BUILD_DIR} @@ -10,78 +29,97 @@ stop() { exit $1 } -BASE_URL='' +check_device_available() { + : ${DEVICE:="chrome"} + failed=false + + if [ "$DEVICE" == "docker" ]; then + [ -x "$(command -v docker)" ] || failed=true + else + cd $DASH_DIR/frontend + npx cypress verify + + case "$DEVICE" in + chrome) + [ -x "$(command -v google-chrome)" ] || [ -x "$(command -v google-chrome-stable)" ] ] || failed=true + ;; + chromium) + [ -x "$(command -v chromium)" ] || failed=true + ;; + esac + fi + + if [ "$failed" = "true" ]; then + echo "ERROR: $DEVICE not found. You need to install $DEVICE or \ + use a different device. Supported devices: chrome (default), chromium, electron or docker." + stop 1 + fi +} + +CYPRESS_BASE_URL='' DEVICE='' -E2E_LOGIN_USER='' -E2E_LOGIN_PWD='' +CYPRESS_LOGIN_PWD='' +CYPRESS_LOGIN_USER='' +NO_COLOR=1 +RECORD='' REMOTE='false' while getopts 'd:p:r:u:' flag; do case "${flag}" in d) DEVICE=$OPTARG;; - p) E2E_LOGIN_PWD=$OPTARG;; + p) CYPRESS_LOGIN_PWD=$OPTARG;; r) REMOTE='true' - BASE_URL=$OPTARG;; - u) E2E_LOGIN_USER=$OPTARG;; + CYPRESS_BASE_URL=$OPTARG;; + u) CYPRESS_LOGIN_USER=$OPTARG;; esac done -if [ "$DEVICE" == "" ]; then - if [ -x "$(command -v google-chrome)" ] || [ -x "$(command -v google-chrome-stable)" ]; then - DEVICE="chrome" - elif [ -x "$(command -v docker)" ]; then - DEVICE="docker" - else - echo "ERROR: Chrome and Docker not found. You need to install one of \ -them to run the e2e frontend tests." - stop 1 - fi -fi - DASH_DIR=`pwd` - [ -z "$BUILD_DIR" ] && BUILD_DIR=build - cd ../../../../${BUILD_DIR} FULL_PATH_BUILD_DIR=`pwd` -if [ "$BASE_URL" == "" ]; then - MGR=2 RGW=1 ../src/vstart.sh -n -d - sleep 10 +[[ "$(command -v npm)" == '' ]] && . ${FULL_PATH_BUILD_DIR}/src/pybind/mgr/dashboard/node-env/bin/activate - # Create an Object Gateway User - ./bin/radosgw-admin user create --uid=dev --display-name=Developer --system - # Set the user-id - ./bin/ceph dashboard set-rgw-api-user-id dev - # Obtain and set access and secret key for the previously created user. $() is safer than backticks `..` - ./bin/ceph dashboard set-rgw-api-access-key $(./bin/radosgw-admin user info --uid=dev | jq -r .keys[0].access_key) - ./bin/ceph dashboard set-rgw-api-secret-key $(./bin/radosgw-admin user info --uid=dev | jq -r .keys[0].secret_key) - # Set SSL verify to False - ./bin/ceph dashboard set-rgw-api-ssl-verify False +: ${CYPRESS_CACHE_FOLDER:="${FULL_PATH_BUILD_DIR}/src/pybind/mgr/dashboard/cypress"} - BASE_URL=$(./bin/ceph mgr services | jq -r .dashboard) -fi +export CYPRESS_BASE_URL CYPRESS_CACHE_FOLDER CYPRESS_LOGIN_USER CYPRESS_LOGIN_PWD NO_COLOR -export BASE_URL E2E_LOGIN_USER E2E_LOGIN_PWD +check_device_available -cd $DASH_DIR/frontend +if [ "$CYPRESS_BASE_URL" == "" ]; then + start_ceph +fi -[[ "$(command -v npm)" == '' ]] && . ${FULL_PATH_BUILD_DIR}/src/pybind/mgr/dashboard/node-env/bin/activate +cd $DASH_DIR/frontend -if [ "$DEVICE" == "chrome" ]; then - npm run e2e:ci || stop 1 - stop 0 -elif [ "$DEVICE" == "docker" ]; then - failed=0 - cat < .env -BASE_URL -E2E_LOGIN_USER -E2E_LOGIN_PWD -EOF - docker run --rm -v $(pwd):/ceph --env-file .env --name=e2e --network=host --entrypoint "" \ - docker.io/rhcsdashboard/e2e npm run e2e:ci || failed=1 - stop $failed -else - echo "ERROR: Device not recognized. Valid devices are 'chrome' and 'docker'." - stop 1 +if [ -n "$CYPRESS_RECORD_KEY" ]; then + RECORD="--record --key $CYPRESS_RECORD_KEY" fi + +case "$DEVICE" in + electron) + npx cypress run $RECORD || stop 1 + ;; + chrome) + npx cypress run $RECORD --browser chrome --headless || stop 1 + ;; + chromium) + npx cypress run $RECORD --browser chromium --headless || stop 1 + ;; + docker) + failed=0 + docker run \ + -v $(pwd):/e2e \ + -w /e2e \ + --env CYPRESS_BASE_URL \ + --env CYPRESS_LOGIN_USER \ + --env CYPRESS_LOGIN_PWD \ + --name=e2e \ + --network=host \ + cypress/included:4.4.0 || failed=1 + stop $failed + ;; +esac + +stop 0