feat(test): Playwright testing integration (#4146)
This commit is contained in:
parent
856cd17531
commit
fe1a706446
27
.github/workflows/playwright.yml
vendored
Normal file
27
.github/workflows/playwright.yml
vendored
Normal file
@ -0,0 +1,27 @@
|
||||
name: Playwright Tests
|
||||
on:
|
||||
push:
|
||||
branches: [main, master]
|
||||
pull_request:
|
||||
branches: [main, master]
|
||||
jobs:
|
||||
test:
|
||||
timeout-minutes: 60
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: lts/*
|
||||
- name: Install dependencies
|
||||
run: npm install -g yarn && yarn
|
||||
- name: Install Playwright Browsers
|
||||
run: yarn playwright install --with-deps
|
||||
- name: Run Playwright tests
|
||||
run: yarn test:e2e:ci
|
||||
- uses: actions/upload-artifact@v4
|
||||
if: always()
|
||||
with:
|
||||
name: playwright-report
|
||||
path: tests/playwright-report
|
||||
retention-days: 30
|
||||
10
.gitignore
vendored
10
.gitignore
vendored
@ -33,7 +33,7 @@ docker/dcm4che/dcm4che-arc
|
||||
|
||||
# Cypress test results
|
||||
videos/
|
||||
screenshots/
|
||||
|
||||
|
||||
# Locize settings
|
||||
.locize
|
||||
@ -46,3 +46,11 @@ platform/app/.recipes/OpenResty-Orthanc/logs/*
|
||||
.vercel
|
||||
|
||||
.vs
|
||||
|
||||
# PlayWright
|
||||
|
||||
node_modules/
|
||||
tests/test-results/
|
||||
tests/playwright-report/
|
||||
/blob-report/
|
||||
/playwright/.cache/
|
||||
|
||||
5
nx.json
5
nx.json
@ -10,7 +10,6 @@
|
||||
"test:unit:ci",
|
||||
"test",
|
||||
"test:e2e",
|
||||
"test:e2e:ci",
|
||||
"test:e2e:local",
|
||||
"test:e2e:dist",
|
||||
"test:e2e:serve",
|
||||
@ -38,8 +37,8 @@
|
||||
"test:e2e": {
|
||||
"dependsOn": ["^test:e2e"]
|
||||
},
|
||||
"test:e2e:ci": {
|
||||
"dependsOn": ["^test:e2e:ci"]
|
||||
"test:e2e:headed": {
|
||||
"dependsOn": ["^test:e2e:headed"]
|
||||
},
|
||||
"test:e2e:local": {
|
||||
"dependsOn": ["^test:e2e:local"]
|
||||
|
||||
@ -45,7 +45,8 @@
|
||||
"test:unit": "jest --collectCoverage",
|
||||
"test:unit:ci": "lerna run test:unit:ci --parallel --stream",
|
||||
"test:e2e": "lerna run test:e2e --stream",
|
||||
"test:e2e:ci": "lerna run test:e2e:ci --stream",
|
||||
"test:e2e:ci": "npx playwright test",
|
||||
"test:e2e:headed": "npx playwright test --headed",
|
||||
"test:e2e:dist": "lerna run test:e2e:dist --stream",
|
||||
"test:e2e:serve": "yarn test:data && lerna run test:e2e:serve --stream",
|
||||
"see-changed": "lerna changed",
|
||||
|
||||
1
platform/app/.gitignore
vendored
1
platform/app/.gitignore
vendored
@ -1 +1,2 @@
|
||||
.vercel
|
||||
test-results
|
||||
|
||||
@ -1,25 +0,0 @@
|
||||
import '@percy/cypress';
|
||||
|
||||
describe('OHIF Percy Layout Advanced Presets', () => {
|
||||
beforeEach(() => {
|
||||
cy.openStudyInViewer('1.3.6.1.4.1.25403.345050719074.3824.20170125113417.1');
|
||||
cy.wait(5000);
|
||||
cy.expectMinimumThumbnails(3);
|
||||
});
|
||||
|
||||
it('should display each layout preset correctly', () => {
|
||||
cy.get('[data-cy="study-browser-thumbnail"]').eq(2).dblclick();
|
||||
cy.selectLayoutPreset('MPR', true);
|
||||
cy.percyCanvasSnapshot('MPR Preset');
|
||||
cy.selectLayoutPreset('3D four up');
|
||||
cy.percyCanvasSnapshot('3D four up Preset');
|
||||
cy.selectLayoutPreset('3D main');
|
||||
cy.percyCanvasSnapshot('3D main Preset');
|
||||
cy.selectLayoutPreset('Axial Primary');
|
||||
cy.percyCanvasSnapshot('Axial Primary Preset');
|
||||
cy.selectLayoutPreset('3D only');
|
||||
cy.percyCanvasSnapshot('3D only Preset');
|
||||
cy.selectLayoutPreset('3D primary');
|
||||
cy.percyCanvasSnapshot('3D primary Preset');
|
||||
});
|
||||
});
|
||||
@ -1,31 +0,0 @@
|
||||
import '@percy/cypress';
|
||||
|
||||
describe('OHIF Percy MPR Crosshairs', () => {
|
||||
beforeEach(() => {
|
||||
cy.openStudyInViewer(
|
||||
'1.3.6.1.4.1.14519.5.2.1.7009.2403.334240657131972136850343327463',
|
||||
'&hangingProtocolId=default',
|
||||
'/viewer'
|
||||
);
|
||||
cy.wait(5000);
|
||||
cy.expectMinimumThumbnails(4);
|
||||
});
|
||||
|
||||
it('should enabled crosshairs in MPR and reset them when another series is loaded', () => {
|
||||
cy.get('[data-cy="study-browser-thumbnail"]').eq(2).dblclick();
|
||||
cy.selectLayoutPreset('MPR');
|
||||
cy.get('[data-cy="Crosshairs"]').click();
|
||||
cy.wait(500);
|
||||
cy.percyCanvasSnapshot('Crosshairs enabled');
|
||||
cy.wait(100);
|
||||
cy.get('[data-viewport-uid="mpr-axial"] > .viewport-element > .cornerstone-canvas').click(
|
||||
45,
|
||||
100
|
||||
);
|
||||
cy.wait(200);
|
||||
cy.percyCanvasSnapshot('Crosshairs are moved');
|
||||
cy.get('[data-cy="study-browser-thumbnail"]').eq(0).dblclick();
|
||||
cy.wait(500);
|
||||
cy.percyCanvasSnapshot('Another series dropped, Crosshairs are centered');
|
||||
});
|
||||
});
|
||||
@ -1,13 +0,0 @@
|
||||
import '@percy/cypress';
|
||||
|
||||
describe('OHIF Percy PDF Display', () => {
|
||||
beforeEach(() => {
|
||||
cy.openStudyInViewer('1.2.826.0.13854362241694438965858641723883466450351448');
|
||||
cy.wait(5000);
|
||||
cy.expectMinimumThumbnails(1);
|
||||
});
|
||||
|
||||
it('should display PDFs correctly', () => {
|
||||
cy.percyCanvasSnapshot('PDF');
|
||||
});
|
||||
});
|
||||
@ -1,26 +0,0 @@
|
||||
import '@percy/cypress';
|
||||
|
||||
describe('OHIF Percy SR', () => {
|
||||
beforeEach(() => {
|
||||
cy.openStudyInViewer('1.2.840.113619.2.5.1762583153.215519.978957063.78');
|
||||
cy.wait(5000);
|
||||
cy.expectMinimumThumbnails(3);
|
||||
});
|
||||
|
||||
it('should hydrate SR correctly and Jump to Measurements', () => {
|
||||
cy.get('[data-cy="study-browser-thumbnail-no-image"]').first().dblclick();
|
||||
cy.wait(200);
|
||||
cy.percyCanvasSnapshot('SR Preview');
|
||||
cy.get('body').type('{enter}');
|
||||
cy.wait(200);
|
||||
cy.percyCanvasSnapshot('SR Hydrated');
|
||||
cy.get('[data-cy="trackedMeasurements-btn"]').click();
|
||||
cy.percyCanvasSnapshot('Opened Measurement Panel');
|
||||
cy.get('body').type('{downarrow}');
|
||||
cy.get('body').type('{downarrow}');
|
||||
cy.get('body').type('{downarrow}');
|
||||
cy.percyCanvasSnapshot('Scrolled away from measurements');
|
||||
cy.get('[data-cy="measurement-item"]').first().click();
|
||||
cy.percyCanvasSnapshot('Jumped to Measurement');
|
||||
});
|
||||
});
|
||||
@ -1,42 +0,0 @@
|
||||
import '@percy/cypress';
|
||||
|
||||
describe('OHIF Percy Segmentation Tools', () => {
|
||||
beforeEach(() => {
|
||||
Cypress.on('uncaught:exception', () => false);
|
||||
cy.openStudyInViewer(
|
||||
'1.3.6.1.4.1.25403.345050719074.3824.20170125113417.1',
|
||||
'&hangingProtocolId=default',
|
||||
'/segmentation'
|
||||
);
|
||||
cy.wait(5000);
|
||||
cy.expectMinimumThumbnails(3);
|
||||
});
|
||||
|
||||
it('should be able to use all segmentation tools and have them render correctly', () => {
|
||||
cy.get('[data-cy="study-browser-thumbnail"]').eq(1).dblclick();
|
||||
cy.percyCanvasSnapshot('Segmentation tools are disabled');
|
||||
cy.get('span').contains('Add segmentation').should('have.css', 'pointer-events', 'none');
|
||||
cy.percyCanvasSnapshot(
|
||||
'Add segmentation button is disabled when the displayset is none reconstructable'
|
||||
);
|
||||
cy.get('[data-cy="study-browser-thumbnail"]').eq(2).dblclick();
|
||||
cy.get('span').contains('Add segmentation').click();
|
||||
cy.wait(500);
|
||||
cy.percyCanvasSnapshot('Segmentation added and tools are enabled');
|
||||
cy.get('[data-cy="Brush"]').click();
|
||||
cy.percyCanvasSnapshot('Brush tool selected');
|
||||
cy.addBrush('.cornerstone-canvas');
|
||||
cy.percyCanvasSnapshot('Brush tool applied');
|
||||
cy.get('[data-cy="Eraser"]').click();
|
||||
cy.percyCanvasSnapshot('Eraser tool selected');
|
||||
cy.addEraser('.cornerstone-canvas');
|
||||
cy.percyCanvasSnapshot('Eraser tool applied');
|
||||
cy.selectLayoutPreset('MPR');
|
||||
cy.get('[data-cy="Brush"]').click();
|
||||
cy.percyCanvasSnapshot('Brush tool selected in MPR');
|
||||
cy.get('button').contains('Sphere').click();
|
||||
cy.percyCanvasSnapshot('Sphere mode for Brush tool selected');
|
||||
cy.addBrush('[data-viewport-uid="mpr-axial"] > .viewport-element > .cornerstone-canvas');
|
||||
cy.percyCanvasSnapshot('Sphere stroke for Brush tool applied');
|
||||
});
|
||||
});
|
||||
@ -1,38 +0,0 @@
|
||||
import '@percy/cypress';
|
||||
|
||||
describe('OHIF Percy TMTV', () => {
|
||||
beforeEach(() => {
|
||||
cy.openStudyInViewer(
|
||||
'1.3.6.1.4.1.14519.5.2.1.7009.2403.334240657131972136850343327463',
|
||||
'',
|
||||
'/tmtv'
|
||||
);
|
||||
cy.wait(10000);
|
||||
});
|
||||
|
||||
it('should maintain VOI when going 1 up and back', () => {
|
||||
cy.percyCanvasSnapshot('TMTV Loaded');
|
||||
cy.get('[data-viewport-uid="mipSagittal"] > .viewport-element > .cornerstone-canvas').dblclick(
|
||||
100,
|
||||
100
|
||||
);
|
||||
cy.wait(500);
|
||||
cy.percyCanvasSnapshot('TMTV One up view loaded');
|
||||
cy.get('[data-viewport-uid="mipSagittal"] > .viewport-element > .cornerstone-canvas').dblclick(
|
||||
100,
|
||||
100
|
||||
);
|
||||
cy.wait(500);
|
||||
cy.percyCanvasSnapshot('TMTV Back to original view');
|
||||
});
|
||||
|
||||
it('should still look fine when changing patient data', () => {
|
||||
cy.get('[data-cy="petSUV-btn"]').click();
|
||||
cy.percyCanvasSnapshot('TMTV Patient Panel Opened');
|
||||
cy.get('[data-cy="input-weight-input"]').type('191');
|
||||
cy.percyCanvasSnapshot('TMTV Patient Panel Weight Changed');
|
||||
cy.get('button').contains('Reload Data').click();
|
||||
cy.wait(5000);
|
||||
cy.percyCanvasSnapshot('TMTV Reloaded Data');
|
||||
});
|
||||
});
|
||||
@ -1,13 +0,0 @@
|
||||
import '@percy/cypress';
|
||||
|
||||
describe('OHIF Percy Video Display', () => {
|
||||
beforeEach(() => {
|
||||
cy.openStudyInViewer('2.25.96975534054447904995905761963464388233');
|
||||
cy.wait(5000);
|
||||
cy.expectMinimumThumbnails(0);
|
||||
});
|
||||
|
||||
it('should display Videos correctly', () => {
|
||||
cy.percyCanvasSnapshot('Video');
|
||||
});
|
||||
});
|
||||
@ -36,7 +36,6 @@
|
||||
"dev:viewer": "yarn run dev",
|
||||
"start": "yarn run dev",
|
||||
"test:e2e": "cypress open",
|
||||
"test:e2e:ci": "percy exec -- cypress run --config video=false --browser chrome --spec 'cypress/integration/visual-regression/**/*'",
|
||||
"test:e2e:local": "cypress run --config video=false --browser chrome --spec 'cypress/integration/common/**/*,cypress/integration/pwa/**/*'",
|
||||
"test:e2e:dist": "start-server-and-test test:e2e:serve http://localhost:3000 test:e2e:ci",
|
||||
"test:e2e:serve": "cross-env APP_CONFIG=config/e2e.js yarn start",
|
||||
@ -99,6 +98,8 @@
|
||||
"devDependencies": {
|
||||
"@babel/plugin-proposal-private-methods": "^7.18.6",
|
||||
"@percy/cypress": "^3.1.1",
|
||||
"@playwright/test": "^1.44.0",
|
||||
"@types/node": "^20.12.12",
|
||||
"cypress": "13.7.2",
|
||||
"cypress-file-upload": "^3.5.3",
|
||||
"glob": "^8.0.3",
|
||||
|
||||
@ -270,6 +270,7 @@ function WorkList({
|
||||
|
||||
return {
|
||||
dataCY: `studyRow-${studyInstanceUid}`,
|
||||
clickableCY: studyInstanceUid,
|
||||
row: [
|
||||
{
|
||||
key: 'patientName',
|
||||
@ -630,7 +631,7 @@ function _getQueryFilterValues(params) {
|
||||
sortBy: params.get('sortby'),
|
||||
sortDirection: params.get('sortdirection'),
|
||||
pageNumber: _tryParseInt(params.get('pagenumber'), undefined),
|
||||
resultsPerPage: _tryParseInt(params.get('resultsperpage'), undefined),
|
||||
resultsPerPage: _tryParseInt(params.get('resultsPerPage'), undefined),
|
||||
datasources: params.get('datasources'),
|
||||
configUrl: params.get('configurl'),
|
||||
};
|
||||
|
||||
138
platform/docs/docs/testing.md
Normal file
138
platform/docs/docs/testing.md
Normal file
@ -0,0 +1,138 @@
|
||||
---
|
||||
sidebar_position: 12
|
||||
sidebar_label: Testing
|
||||
---
|
||||
|
||||
# Writing PlayWright Tests
|
||||
|
||||
Our Playwright tests are written using the Playwright test framework. We use these tests to test our OHIF Viewer and ensure that it is working as expected.
|
||||
|
||||
In this guide, we will show you how to write Playwright tests for the OHIF Viewer.
|
||||
|
||||
## Using a specific study and mode
|
||||
|
||||
If you would like to use a specific study, you can use the `studyInstanceUID` property to reference the study you would like to visit. for example, if you would like to use the study with StudyInstanceUID `2.16.840.1.114362.1.11972228.22789312658.616067305.306.2` and the mode `Basic Viewer`, you can use the following code snippet:
|
||||
|
||||
```ts
|
||||
import { test } from '@playwright/test';
|
||||
import { visitStudy, checkForScreenshot, screenShotPaths } from './utils/index';
|
||||
|
||||
test.beforeEach(async ({ page }) => {
|
||||
const studyInstanceUID = '2.16.840.1.114362.1.11972228.22789312658.616067305.306.2';
|
||||
const mode = 'Basic Viewer';
|
||||
await visitStudy(page, studyInstanceUID, mode);
|
||||
});
|
||||
|
||||
test.describe('Some Test', async () => {
|
||||
test('should do something.', async ({ page }) => {
|
||||
// Your test code here...
|
||||
});
|
||||
});
|
||||
|
||||
```
|
||||
|
||||
## Screenshots
|
||||
|
||||
A good way to check your tests is working as expected is to capture screenshots at different stages of the test. You can use our `checkForScreenshot` function located in `tests/utils/checkForScreenshot.ts` to capture screenshots. You should also plan your screenshots in advance, screenshots need to be defined in the `tests/utils/screenshotPaths.ts` file. For example, if you would to capture a screenshot after a measurement is added, you can define a screenshot path like this:
|
||||
|
||||
```ts
|
||||
const screenShotPaths = {
|
||||
your_test_name: {
|
||||
measurementAdded: 'measurementAdded.png',
|
||||
measurementRemoved: 'measurementRemoved.png',
|
||||
},
|
||||
};
|
||||
```
|
||||
|
||||
It's okay if the screenshot doesn't exist yet, this will be dealt with in the next step. Once you have defined your screenshot path, you can use the `checkForScreenshot` function in your test to capture the screenshot. For example, if you would like to capture a screenshot of the page after a measurement is added, you can use the following code snippet:
|
||||
|
||||
```ts
|
||||
import { test } from '@playwright/test';
|
||||
import {
|
||||
visitStudy,
|
||||
checkForScreenshot,
|
||||
screenshotPath,
|
||||
} from './utils/index';
|
||||
|
||||
test.beforeEach(async ({ page }) => {
|
||||
const studyInstanceUID = '2.16.840.1.114362.1.11972228.22789312658.616067305.306.2';
|
||||
const mode = 'Basic Viewer';
|
||||
await visitStudy(page, studyInstanceUID, mode);
|
||||
});
|
||||
|
||||
test.describe('Some test', async () => {
|
||||
test('should do something', async ({ page }) => {
|
||||
// Your test code here to add a measurement
|
||||
await checkForScreenshot(
|
||||
page,
|
||||
page,
|
||||
screenshotPath.your_test_name.measurementAdded
|
||||
);
|
||||
});
|
||||
});
|
||||
```
|
||||
|
||||
The test will automatically fail the first time you run it, it will however generate the screenshot for you, you will notice 3 new entries in the `tests/screenshots` folder, under `chromium/your-test.spec.js/measurementAdded.png`, `firefox/your-test.spec.js/measurementAdded.png` and `webkit/your-test.spec.js/measurementAdded.png` folders. You can now run the test again and it will use those screenshots to compare against the current state of the example. Please verify that the ground truth screenshots are correct before committing them or testing against them.
|
||||
|
||||
## Simulating mouse drags
|
||||
|
||||
If you would like to simulate a mouse drag, you can use the `simulateDrag` function located in `tests/utils/simulateDrag.ts`. You can use this function to simulate a mouse drag on an element. For example, if you would like to simulate a mouse drag on the `cornerstone-canvas` element, you can use the following code snippet:
|
||||
|
||||
```ts
|
||||
import {
|
||||
visitStudy,
|
||||
checkForScreenshot,
|
||||
screenShotPaths,
|
||||
simulateDrag,
|
||||
} from './utils/index';
|
||||
|
||||
test.beforeEach(async ({ page }) => {
|
||||
const studyInstanceUID = '2.16.840.1.114362.1.11972228.22789312658.616067305.306.2';
|
||||
const mode = 'Basic Viewer';
|
||||
await visitStudy(page, studyInstanceUID, mode);
|
||||
});
|
||||
|
||||
test.describe('Some Test', async () => {
|
||||
test('should do something..', async ({
|
||||
page,
|
||||
}) => {
|
||||
const locator = page.locator('.cornerstone-canvas');
|
||||
await simulateDrag(page, locator);
|
||||
});
|
||||
});
|
||||
```
|
||||
|
||||
Our simulate drag utility can simulate a drag on any element, and avoid going out of bounds. It will calculuate the bounding box of the element and ensure that the drag stays within the bounds of the element. This should be good enough for most tools, and better than providing custom x, and y coordinates which can be error prone and make the code difficult to maintain.
|
||||
|
||||
## Running the tests
|
||||
|
||||
After you have wrote your tests, you can run them by using the following command:
|
||||
|
||||
```bash
|
||||
yarn test:e2e:ci
|
||||
```
|
||||
|
||||
If you want to use headed mode, you can use the following command:
|
||||
|
||||
```bash
|
||||
yarn test:e2e:headed
|
||||
```
|
||||
|
||||
You will see the test results in your terminal, if you want an indepth report, you can use the following command:
|
||||
|
||||
```bash
|
||||
yarn playwright show-report tests/playwright-report
|
||||
```
|
||||
|
||||
## Serving the viewer manually for development
|
||||
|
||||
By default, when you run the tests, it will call the `yarn start` command to serve the viewer first, then run the tests, if you would like to serve the viewer manually, you can use the same command. The viewer will be available at `http://localhost:3000`. This could speed up your development process since playwright will skip this step and use the existing server on port 3000.
|
||||
|
||||
## Playwright VSCode Extension and Recording Tests
|
||||
|
||||
If you are using VSCode, you can use the Playwright extension to help you write your tests. The extension provides a test runner and many great features such as picking a locator using your mouse, recording a new test, and more. You can install the extension by searching for `Playwright` in the extensions tab in VSCode or by visiting the [Playwright extension page](https://marketplace.visualstudio.com/items?itemName=ms-playwright.playwright). The below recording is for CornestoneJS, but the same principles apply to OHIF Viewer.
|
||||
|
||||
<div style={{padding:"56.25% 0 0 0", position:"relative"}}>
|
||||
<iframe src="https://player.vimeo.com/video/947471524?h=6720407fef&badge=0&autopause=0&player_id=0&app_id=58479"
|
||||
frameBorder="0" allow="cross-origin-isolated" allowFullScreen style= {{ position:"absolute",top:0,left:0,width:"100%",height:"100%"}} title="Playwright Extension"></iframe>
|
||||
</div>
|
||||
@ -84,6 +84,7 @@ const InvestigationalUseDialog = ({ dialogConfiguration }) => {
|
||||
type={ButtonEnums.type.primary}
|
||||
onClick={handleConfirmAndHide}
|
||||
className="bg-primary-main"
|
||||
dataCY="confirm-and-hide-button"
|
||||
>
|
||||
Confirm and Hide
|
||||
</Button>
|
||||
|
||||
@ -7,7 +7,7 @@ import Icon from '../Icon';
|
||||
|
||||
const StudyListTableRow = props => {
|
||||
const { tableData } = props;
|
||||
const { row, expandedContent, onClickRow, isExpanded, dataCY } = tableData;
|
||||
const { row, expandedContent, onClickRow, isExpanded, dataCY, clickableCY } = tableData;
|
||||
return (
|
||||
<>
|
||||
<tr
|
||||
@ -42,6 +42,7 @@ const StudyListTableRow = props => {
|
||||
{ 'bg-secondary-dark': isExpanded }
|
||||
)}
|
||||
onClick={onClickRow}
|
||||
data-cy={clickableCY}
|
||||
>
|
||||
{row.map((cell, index) => {
|
||||
const { content, title, gridCol } = cell;
|
||||
@ -108,6 +109,7 @@ StudyListTableRow.propTypes = {
|
||||
onClickRow: PropTypes.func.isRequired,
|
||||
isExpanded: PropTypes.bool.isRequired,
|
||||
dataCY: PropTypes.string,
|
||||
clickableCY: PropTypes.string,
|
||||
}),
|
||||
};
|
||||
|
||||
|
||||
44
playwright.config.ts
Normal file
44
playwright.config.ts
Normal file
@ -0,0 +1,44 @@
|
||||
import { defineConfig, devices } from '@playwright/test';
|
||||
|
||||
export default defineConfig({
|
||||
testDir: './tests',
|
||||
fullyParallel: true,
|
||||
forbidOnly: !!process.env.CI,
|
||||
retries: process.env.CI ? 2 : 0,
|
||||
workers: process.env.CI ? 1 : undefined,
|
||||
snapshotPathTemplate: './tests/screenshots{/projectName}/{testFilePath}/{arg}{ext}',
|
||||
outputDir: './tests/test-results',
|
||||
reporter: [['html', { outputFolder: './tests/playwright-report' }]],
|
||||
timeout: 120 * 1000,
|
||||
use: {
|
||||
baseURL: 'http://localhost:3000',
|
||||
trace: 'on-first-retry',
|
||||
video: 'on',
|
||||
testIdAttribute: 'data-cy',
|
||||
},
|
||||
|
||||
projects: [
|
||||
{
|
||||
name: 'chromium',
|
||||
use: { ...devices['Desktop Chrome'], deviceScaleFactor: 1 },
|
||||
},
|
||||
// TODO: Fix firefox tests
|
||||
// {
|
||||
// name: 'firefox',
|
||||
// use: { ...devices['Desktop Firefox'], deviceScaleFactor: 1 },
|
||||
// },
|
||||
// This is commented out until SharedArrayBuffer is enabled in WebKit
|
||||
// See: https://github.com/microsoft/playwright/issues/14043
|
||||
|
||||
//{
|
||||
// name: 'webkit',
|
||||
// use: { ...devices['Desktop Safari'], deviceScaleFactor: 1 },
|
||||
//},
|
||||
],
|
||||
webServer: {
|
||||
command: 'yarn start',
|
||||
url: 'http://localhost:3000',
|
||||
reuseExistingServer: !process.env.CI,
|
||||
timeout: 120 * 1000,
|
||||
},
|
||||
});
|
||||
16
tests/MPR.spec.ts
Normal file
16
tests/MPR.spec.ts
Normal file
@ -0,0 +1,16 @@
|
||||
import { test } from '@playwright/test';
|
||||
import { visitStudy, checkForScreenshot, screenShotPaths } from './utils/index';
|
||||
|
||||
test.beforeEach(async ({ page }) => {
|
||||
const studyInstanceUID = '2.16.840.1.114362.1.11972228.22789312658.616067305.306.2';
|
||||
const mode = 'Basic Viewer';
|
||||
await visitStudy(page, studyInstanceUID, mode);
|
||||
});
|
||||
|
||||
test.describe('MPR Test', async () => {
|
||||
test('should render MPR correctly.', async ({ page }) => {
|
||||
await page.getByTestId('Layout').click();
|
||||
await page.locator('div').filter({ hasText: /^MPR$/ }).first().click();
|
||||
await checkForScreenshot(page, page, screenShotPaths.mpr.mprDisplayedCorrectly);
|
||||
});
|
||||
});
|
||||
BIN
tests/screenshots/chromium/MPR.spec.ts/mprDisplayedCorrectly.png
Normal file
BIN
tests/screenshots/chromium/MPR.spec.ts/mprDisplayedCorrectly.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 232 KiB |
35
tests/utils/checkForScreenshot.ts
Normal file
35
tests/utils/checkForScreenshot.ts
Normal file
@ -0,0 +1,35 @@
|
||||
import { expect } from '@playwright/test';
|
||||
import { Locator, Page } from 'playwright';
|
||||
|
||||
/**
|
||||
* @param page - The page to interact with
|
||||
* @param locator - The element to check for screenshot
|
||||
* @param screenshotPath - The path to save the screenshot
|
||||
* @param attempts - The number of attempts to check for screenshot
|
||||
* @param delay - The delay between attempts
|
||||
* @returns True if the screenshot matches, otherwise throws an error
|
||||
*/
|
||||
const checkForScreenshot = async (
|
||||
page: Page,
|
||||
locator: Locator | Page,
|
||||
screenshotPath: string,
|
||||
attempts = 10,
|
||||
delay = 100
|
||||
) => {
|
||||
await page.waitForLoadState('networkidle');
|
||||
for (let i = 1; i < attempts; i++) {
|
||||
try {
|
||||
await expect(locator).toHaveScreenshot(screenshotPath, {
|
||||
maxDiffPixelRatio: 0.1,
|
||||
});
|
||||
return true;
|
||||
} catch (error) {
|
||||
if (i === attempts) {
|
||||
throw new Error('Screenshot does not match.');
|
||||
}
|
||||
await new Promise(resolve => setTimeout(resolve, delay));
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
export { checkForScreenshot };
|
||||
6
tests/utils/index.ts
Normal file
6
tests/utils/index.ts
Normal file
@ -0,0 +1,6 @@
|
||||
import { visitStudy } from './visitStudy';
|
||||
import { checkForScreenshot } from './checkForScreenshot';
|
||||
import { screenShotPaths } from './screenShotPaths';
|
||||
import { simulateDrag } from './simulateDrag';
|
||||
|
||||
export { visitStudy, checkForScreenshot, screenShotPaths, simulateDrag };
|
||||
10
tests/utils/screenShotPaths.ts
Normal file
10
tests/utils/screenShotPaths.ts
Normal file
@ -0,0 +1,10 @@
|
||||
/**
|
||||
* Paths to the screenshots of the tests.
|
||||
*/
|
||||
const screenShotPaths = {
|
||||
mpr: {
|
||||
mprDisplayedCorrectly: 'mprDisplayedCorrectly.png',
|
||||
},
|
||||
};
|
||||
|
||||
export { screenShotPaths };
|
||||
29
tests/utils/simulateDrag.ts
Normal file
29
tests/utils/simulateDrag.ts
Normal file
@ -0,0 +1,29 @@
|
||||
import { Locator, Page } from 'playwright';
|
||||
|
||||
/**
|
||||
*
|
||||
* @param page - The page to simulate the drag on
|
||||
* @param locator - The locator of the element to perform the drag on
|
||||
*/
|
||||
|
||||
export async function simulateDrag(page: Page, locator: Locator) {
|
||||
const box = await locator.boundingBox();
|
||||
if (!box) {
|
||||
throw new Error('Element is not visible');
|
||||
}
|
||||
const { x, y, width, height } = box;
|
||||
const centerX = x + width / 2;
|
||||
const centerY = y + height / 2;
|
||||
|
||||
// Calculate the maximum possible movement distances within the element's bounds
|
||||
const maxMoveX = Math.min(100, x + width - centerX);
|
||||
const maxMoveY = Math.min(100, y + height - centerY);
|
||||
|
||||
const newX = centerX + maxMoveX;
|
||||
const newY = centerY + maxMoveY;
|
||||
|
||||
await page.mouse.move(centerX, centerY);
|
||||
await page.mouse.down();
|
||||
await page.mouse.move(newX, newY);
|
||||
await page.mouse.up();
|
||||
}
|
||||
16
tests/utils/visitStudy.ts
Normal file
16
tests/utils/visitStudy.ts
Normal file
@ -0,0 +1,16 @@
|
||||
import { Page } from '@playwright/test';
|
||||
|
||||
/**
|
||||
* Visit the study
|
||||
* @param page - The page to interact with
|
||||
* @param title - The study instance UID of the study to visit
|
||||
* @param mode - The mode to visit the study in
|
||||
*/
|
||||
export async function visitStudy(page: Page, studyInstanceUID: string, mode: string) {
|
||||
await page.goto('/?resultsPerPage=100');
|
||||
await page.getByTestId('confirm-and-hide-button').click();
|
||||
await page.getByTestId(studyInstanceUID).click();
|
||||
await page.getByRole('button', { name: mode }).click();
|
||||
await page.waitForLoadState('domcontentloaded');
|
||||
await page.waitForLoadState('networkidle');
|
||||
}
|
||||
110
yarn.lock
110
yarn.lock
@ -1575,7 +1575,6 @@
|
||||
pako "^2.0.4"
|
||||
uuid "^9.0.0"
|
||||
|
||||
|
||||
"@cornerstonejs/streaming-image-volume-loader@^1.71.2":
|
||||
version "1.71.2"
|
||||
resolved "https://registry.yarnpkg.com/@cornerstonejs/streaming-image-volume-loader/-/streaming-image-volume-loader-1.71.2.tgz#64b92c71f1550000edaa79f10259ebec05092748"
|
||||
@ -2087,7 +2086,7 @@
|
||||
"@docusaurus/theme-search-algolia" "2.4.3"
|
||||
"@docusaurus/types" "2.4.3"
|
||||
|
||||
"@docusaurus/react-loadable@5.5.2", "react-loadable@npm:@docusaurus/react-loadable@5.5.2":
|
||||
"@docusaurus/react-loadable@5.5.2":
|
||||
version "5.5.2"
|
||||
resolved "https://registry.yarnpkg.com/@docusaurus/react-loadable/-/react-loadable-5.5.2.tgz#81aae0db81ecafbdaee3651f12804580868fa6ce"
|
||||
integrity sha512-A3dYjdBGuy0IGT+wyLIGIKLRE+sAk1iNk0f1HjNDysO7u8lhL4N3VEm+FAubmJbAztn94F7MxBTPmnixbiyFdQ==
|
||||
@ -3410,6 +3409,13 @@
|
||||
resolved "https://registry.yarnpkg.com/@pkgr/core/-/core-0.1.1.tgz#1ec17e2edbec25c8306d424ecfbf13c7de1aaa31"
|
||||
integrity sha512-cq8o4cWH0ibXh9VGi5P20Tu9XF/0fFXl9EUinr9QfTM7a7p0oTA4iJRCQWppXR1Pg8dSM0UCItCkPwsk9qWWYA==
|
||||
|
||||
"@playwright/test@^1.44.0":
|
||||
version "1.44.0"
|
||||
resolved "https://registry.yarnpkg.com/@playwright/test/-/test-1.44.0.tgz#ac7a764b5ee6a80558bdc0fcbc525fcb81f83465"
|
||||
integrity sha512-rNX5lbNidamSUorBhB4XZ9SQTjAqfe5M+p37Z8ic0jPFBMo5iCtQz1kRWkEMg+rYOKSlVycpQmpqjSFq7LXOfg==
|
||||
dependencies:
|
||||
playwright "1.44.0"
|
||||
|
||||
"@pmmmwh/react-refresh-webpack-plugin@^0.5.11":
|
||||
version "0.5.11"
|
||||
resolved "https://registry.yarnpkg.com/@pmmmwh/react-refresh-webpack-plugin/-/react-refresh-webpack-plugin-0.5.11.tgz#7c2268cedaa0644d677e8c4f377bc8fb304f714a"
|
||||
@ -5263,6 +5269,13 @@
|
||||
dependencies:
|
||||
undici-types "~5.26.4"
|
||||
|
||||
"@types/node@^20.12.12":
|
||||
version "20.12.12"
|
||||
resolved "https://registry.yarnpkg.com/@types/node/-/node-20.12.12.tgz#7cbecdf902085cec634fdb362172dfe12b8f2050"
|
||||
integrity sha512-eWLDGF/FOSPtAvEqeRAQ4C8LSA7M1I7i0ky1I8U7kD1J5ITyW3AsRhQrKVoWf5pFKZ2kILsEGJhsI9r93PYnOw==
|
||||
dependencies:
|
||||
undici-types "~5.26.4"
|
||||
|
||||
"@types/normalize-package-data@^2.4.0":
|
||||
version "2.4.4"
|
||||
resolved "https://registry.yarnpkg.com/@types/normalize-package-data/-/normalize-package-data-2.4.4.tgz#56e2cc26c397c038fab0e3a917a12d5c5909e901"
|
||||
@ -6774,26 +6787,33 @@ bare-events@^2.0.0, bare-events@^2.2.0:
|
||||
integrity sha512-h7z00dWdG0PYOQEvChhOSWvOfkIKsdZGkWr083FgN/HyoQuebSew/cgirYqh9SCuy/hRvxc5Vy6Fw8xAmYHLkQ==
|
||||
|
||||
bare-fs@^2.1.1:
|
||||
version "2.2.3"
|
||||
resolved "https://registry.yarnpkg.com/bare-fs/-/bare-fs-2.2.3.tgz#34f8b81b8c79de7ef043383c05e57d4a10392a68"
|
||||
integrity sha512-amG72llr9pstfXOBOHve1WjiuKKAMnebcmMbPWDZ7BCevAoJLpugjuAPRsDINEyjT0a6tbaVx3DctkXIRbLuJw==
|
||||
version "2.3.0"
|
||||
resolved "https://registry.yarnpkg.com/bare-fs/-/bare-fs-2.3.0.tgz#0872f8e33cf291c9fd527d827154f156a298d402"
|
||||
integrity sha512-TNFqa1B4N99pds2a5NYHR15o0ZpdNKbAeKTE/+G6ED/UeOavv8RY3dr/Fu99HW3zU3pXpo2kDNO8Sjsm2esfOw==
|
||||
dependencies:
|
||||
bare-events "^2.0.0"
|
||||
bare-path "^2.0.0"
|
||||
streamx "^2.13.0"
|
||||
bare-stream "^1.0.0"
|
||||
|
||||
bare-os@^2.1.0:
|
||||
version "2.2.1"
|
||||
resolved "https://registry.yarnpkg.com/bare-os/-/bare-os-2.2.1.tgz#c94a258c7a408ca6766399e44675136c0964913d"
|
||||
integrity sha512-OwPyHgBBMkhC29Hl3O4/YfxW9n7mdTr2+SsO29XBWKKJsbgj3mnorDB80r5TiCQgQstgE5ga1qNYrpes6NvX2w==
|
||||
version "2.3.0"
|
||||
resolved "https://registry.yarnpkg.com/bare-os/-/bare-os-2.3.0.tgz#718e680b139effff0624a7421c098e7a2c2d63da"
|
||||
integrity sha512-oPb8oMM1xZbhRQBngTgpcQ5gXw6kjOaRsSWsIeNyRxGed2w/ARyP7ScBYpWR1qfX2E5rS3gBw6OWcSQo+s+kUg==
|
||||
|
||||
bare-path@^2.0.0, bare-path@^2.1.0:
|
||||
version "2.1.1"
|
||||
resolved "https://registry.yarnpkg.com/bare-path/-/bare-path-2.1.1.tgz#111db5bf2db0aed40081aa4ba38b8dfc2bb782eb"
|
||||
integrity sha512-OHM+iwRDRMDBsSW7kl3dO62JyHdBKO3B25FB9vNQBPcGHMo4+eA8Yj41Lfbk3pS/seDY+siNge0LdRTulAau/A==
|
||||
version "2.1.2"
|
||||
resolved "https://registry.yarnpkg.com/bare-path/-/bare-path-2.1.2.tgz#7a0940d34ebe65f7e179fa61ed8d49d9dc151d67"
|
||||
integrity sha512-o7KSt4prEphWUHa3QUwCxUI00R86VdjiuxmJK0iNVDHYPGo+HsDaVCnqCmPbf/MiW1ok8F4p3m8RTHlWk8K2ig==
|
||||
dependencies:
|
||||
bare-os "^2.1.0"
|
||||
|
||||
bare-stream@^1.0.0:
|
||||
version "1.0.0"
|
||||
resolved "https://registry.yarnpkg.com/bare-stream/-/bare-stream-1.0.0.tgz#25c3e56198d922187320c3f8c52d75c4051178b4"
|
||||
integrity sha512-KhNUoDL40iP4gFaLSsoGE479t0jHijfYdIcxRn/XtezA2BaUD0NRf/JGRpsMq6dMNM+SrCrB0YSSo/5wBY4rOQ==
|
||||
dependencies:
|
||||
streamx "^2.16.1"
|
||||
|
||||
base16@^1.0.0:
|
||||
version "1.0.0"
|
||||
resolved "https://registry.yarnpkg.com/base16/-/base16-1.0.0.tgz#e297f60d7ec1014a7a971a39ebc8a98c0b681e70"
|
||||
@ -10966,6 +10986,11 @@ fs.realpath@^1.0.0:
|
||||
resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f"
|
||||
integrity sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==
|
||||
|
||||
fsevents@2.3.2:
|
||||
version "2.3.2"
|
||||
resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.3.2.tgz#8a526f78b8fdf4623b709e0b975c52c24c02fd1a"
|
||||
integrity sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==
|
||||
|
||||
fsevents@^2.3.2, fsevents@~2.3.2:
|
||||
version "2.3.3"
|
||||
resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.3.3.tgz#cac6407785d03675a2a5e1a5305c697b347d90d6"
|
||||
@ -16420,6 +16445,20 @@ pkg-up@^3.1.0:
|
||||
dependencies:
|
||||
find-up "^3.0.0"
|
||||
|
||||
playwright-core@1.44.0:
|
||||
version "1.44.0"
|
||||
resolved "https://registry.yarnpkg.com/playwright-core/-/playwright-core-1.44.0.tgz#316c4f0bca0551ffb88b6eb1c97bc0d2d861b0d5"
|
||||
integrity sha512-ZTbkNpFfYcGWohvTTl+xewITm7EOuqIqex0c7dNZ+aXsbrLj0qI8XlGKfPpipjm0Wny/4Lt4CJsWJk1stVS5qQ==
|
||||
|
||||
playwright@1.44.0:
|
||||
version "1.44.0"
|
||||
resolved "https://registry.yarnpkg.com/playwright/-/playwright-1.44.0.tgz#22894e9b69087f6beb639249323d80fe2b5087ff"
|
||||
integrity sha512-F9b3GUCLQ3Nffrfb6dunPOkE5Mh68tR7zN32L4jCk4FjQamgesGay7/dAAe1WaMEGV04DkdJfcJzjoCKygUaRQ==
|
||||
dependencies:
|
||||
playwright-core "1.44.0"
|
||||
optionalDependencies:
|
||||
fsevents "2.3.2"
|
||||
|
||||
please-upgrade-node@^3.1.1, please-upgrade-node@^3.2.0:
|
||||
version "3.2.0"
|
||||
resolved "https://registry.yarnpkg.com/please-upgrade-node/-/please-upgrade-node-3.2.0.tgz#aeddd3f994c933e4ad98b99d9a556efa0e2fe942"
|
||||
@ -17744,6 +17783,14 @@ react-loadable-ssr-addon-v5-slorber@^1.0.1:
|
||||
dependencies:
|
||||
"@babel/runtime" "^7.10.3"
|
||||
|
||||
"react-loadable@npm:@docusaurus/react-loadable@5.5.2":
|
||||
version "5.5.2"
|
||||
resolved "https://registry.yarnpkg.com/@docusaurus/react-loadable/-/react-loadable-5.5.2.tgz#81aae0db81ecafbdaee3651f12804580868fa6ce"
|
||||
integrity sha512-A3dYjdBGuy0IGT+wyLIGIKLRE+sAk1iNk0f1HjNDysO7u8lhL4N3VEm+FAubmJbAztn94F7MxBTPmnixbiyFdQ==
|
||||
dependencies:
|
||||
"@types/react" "*"
|
||||
prop-types "^15.6.2"
|
||||
|
||||
react-modal@3.11.2:
|
||||
version "3.11.2"
|
||||
resolved "https://registry.yarnpkg.com/react-modal/-/react-modal-3.11.2.tgz#bad911976d4add31aa30dba8a41d11e21c4ac8a4"
|
||||
@ -19496,7 +19543,7 @@ stream-shift@^1.0.0:
|
||||
resolved "https://registry.yarnpkg.com/stream-shift/-/stream-shift-1.0.3.tgz#85b8fab4d71010fc3ba8772e8046cc49b8a3864b"
|
||||
integrity sha512-76ORR0DO1o1hlKwTbi/DM3EXWGf3ZJYO8cXX5RJwnul2DEg2oyoZyjLNoQM8WsvZiFKCRfC1O0J7iCvie3RZmQ==
|
||||
|
||||
streamx@^2.13.0, streamx@^2.15.0:
|
||||
streamx@^2.15.0, streamx@^2.16.1:
|
||||
version "2.16.1"
|
||||
resolved "https://registry.yarnpkg.com/streamx/-/streamx-2.16.1.tgz#2b311bd34832f08aa6bb4d6a80297c9caef89614"
|
||||
integrity sha512-m9QYj6WygWyWa3H1YY69amr4nVgy61xfjys7xO7kviL5rfIEc2naf+ewFiOA+aEJD7y0JO3h2GoiUv4TDwEGzQ==
|
||||
@ -19534,7 +19581,7 @@ string-natural-compare@^3.0.1:
|
||||
resolved "https://registry.yarnpkg.com/string-natural-compare/-/string-natural-compare-3.0.1.tgz#7a42d58474454963759e8e8b7ae63d71c1e7fdf4"
|
||||
integrity sha512-n3sPwynL1nwKi3WJ6AIsClwBMa0zTi54fn2oLU6ndfTSIO05xaznjSf15PcBZU6FNWbmN5Q6cxT4V5hGvB4taw==
|
||||
|
||||
"string-width-cjs@npm:string-width@^4.2.0", "string-width@^1.0.2 || 2 || 3 || 4", string-width@^4.0.0, string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.2, string-width@^4.2.3:
|
||||
"string-width-cjs@npm:string-width@^4.2.0":
|
||||
version "4.2.3"
|
||||
resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010"
|
||||
integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==
|
||||
@ -19552,6 +19599,15 @@ string-width@^1.0.1:
|
||||
is-fullwidth-code-point "^1.0.0"
|
||||
strip-ansi "^3.0.0"
|
||||
|
||||
"string-width@^1.0.2 || 2 || 3 || 4", string-width@^4.0.0, string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.2, string-width@^4.2.3:
|
||||
version "4.2.3"
|
||||
resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010"
|
||||
integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==
|
||||
dependencies:
|
||||
emoji-regex "^8.0.0"
|
||||
is-fullwidth-code-point "^3.0.0"
|
||||
strip-ansi "^6.0.1"
|
||||
|
||||
string-width@^2.1.0, string-width@^2.1.1:
|
||||
version "2.1.1"
|
||||
resolved "https://registry.yarnpkg.com/string-width/-/string-width-2.1.1.tgz#ab93f27a8dc13d28cac815c462143a6d9012ae9e"
|
||||
@ -19647,7 +19703,7 @@ stringify-object@^3.3.0:
|
||||
is-obj "^1.0.1"
|
||||
is-regexp "^1.0.0"
|
||||
|
||||
"strip-ansi-cjs@npm:strip-ansi@^6.0.1", strip-ansi@^6.0.0, strip-ansi@^6.0.1:
|
||||
"strip-ansi-cjs@npm:strip-ansi@^6.0.1":
|
||||
version "6.0.1"
|
||||
resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9"
|
||||
integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==
|
||||
@ -19675,6 +19731,13 @@ strip-ansi@^5.1.0:
|
||||
dependencies:
|
||||
ansi-regex "^4.1.0"
|
||||
|
||||
strip-ansi@^6.0.0, strip-ansi@^6.0.1:
|
||||
version "6.0.1"
|
||||
resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9"
|
||||
integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==
|
||||
dependencies:
|
||||
ansi-regex "^5.0.1"
|
||||
|
||||
strip-ansi@^7.0.0, strip-ansi@^7.0.1:
|
||||
version "7.1.0"
|
||||
resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-7.1.0.tgz#d5b6568ca689d8561370b0707685d22434faff45"
|
||||
@ -19979,9 +20042,9 @@ tar-fs@^2.0.0, tar-fs@^2.1.1:
|
||||
tar-stream "^2.1.4"
|
||||
|
||||
tar-fs@^3.0.4:
|
||||
version "3.0.5"
|
||||
resolved "https://registry.yarnpkg.com/tar-fs/-/tar-fs-3.0.5.tgz#f954d77767e4e6edf973384e1eb95f8f81d64ed9"
|
||||
integrity sha512-JOgGAmZyMgbqpLwct7ZV8VzkEB6pxXFBVErLtb+XCOqzc6w1xiWKI9GVd6bwk68EX7eJ4DWmfXVmq8K2ziZTGg==
|
||||
version "3.0.6"
|
||||
resolved "https://registry.yarnpkg.com/tar-fs/-/tar-fs-3.0.6.tgz#eaccd3a67d5672f09ca8e8f9c3d2b89fa173f217"
|
||||
integrity sha512-iokBDQQkUyeXhgPYaZxmczGPhnhXZ0CmrqI+MOb/WFGS9DW5wnfrLgtjUJBvz50vQ3qfRwJ62QVoCFu8mPVu5w==
|
||||
dependencies:
|
||||
pump "^3.0.0"
|
||||
tar-stream "^3.1.5"
|
||||
@ -21718,7 +21781,7 @@ worker-loader@3.0.8, worker-loader@^3.0.8:
|
||||
loader-utils "^2.0.0"
|
||||
schema-utils "^3.0.0"
|
||||
|
||||
"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0", wrap-ansi@^7.0.0:
|
||||
"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0":
|
||||
version "7.0.0"
|
||||
resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43"
|
||||
integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==
|
||||
@ -21744,6 +21807,15 @@ wrap-ansi@^6.0.1, wrap-ansi@^6.2.0:
|
||||
string-width "^4.1.0"
|
||||
strip-ansi "^6.0.0"
|
||||
|
||||
wrap-ansi@^7.0.0:
|
||||
version "7.0.0"
|
||||
resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43"
|
||||
integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==
|
||||
dependencies:
|
||||
ansi-styles "^4.0.0"
|
||||
string-width "^4.1.0"
|
||||
strip-ansi "^6.0.0"
|
||||
|
||||
wrap-ansi@^8.0.1, wrap-ansi@^8.1.0:
|
||||
version "8.1.0"
|
||||
resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-8.1.0.tgz#56dc22368ee570face1b49819975d9b9a5ead214"
|
||||
|
||||
Loading…
Reference in New Issue
Block a user