From 1d3b01e07605e17bc841a34de0a83d22507add49 Mon Sep 17 00:00:00 2001 From: Matthew McCormick Date: Fri, 13 Jun 2025 14:46:42 -0400 Subject: [PATCH 1/3] test(itk-wasm): from cypress to playwright --- .github/workflows/cypress.yml | 89 ---- .github/workflows/playwright.yml | 61 +++ package.json | 3 +- packages/core/typescript/itk-wasm/.gitignore | 3 +- .../typescript/itk-wasm/cypress.config.ts | 13 - .../itk-wasm/cypress/e2e/cast-image.cy.ts | 147 ----- .../pipeline/pthread-support-available.cy.ts | 21 - .../cypress/e2e/pipeline/run-pipeline.cy.ts | 315 ----------- .../cypress/e2e/web-worker-pool.cy.ts | 58 -- .../itk-wasm/cypress/support/commands.ts | 37 -- .../cypress/support/compareImageToBaseline.ts | 9 - .../itk-wasm/cypress/support/e2e.ts | 20 - .../typescript/itk-wasm/cypress/tsconfig.json | 12 - .../core/typescript/itk-wasm/package.json | 22 +- .../typescript/itk-wasm/playwright.config.js | 81 +++ .../itk-wasm/test/browser/cast-image.spec.js | 199 +++++++ .../e2e/common.ts => test/browser/common.js} | 10 +- .../test/browser/compare-image-to-baseline.js | 12 + .../pthread-support-available.spec.js | 26 + .../browser/pipeline/run-pipeline.spec.js | 502 ++++++++++++++++++ .../test/browser/web-worker-pool.spec.js | 84 +++ pnpm-lock.yaml | 47 +- 22 files changed, 1020 insertions(+), 751 deletions(-) delete mode 100644 packages/core/typescript/itk-wasm/cypress.config.ts delete mode 100644 packages/core/typescript/itk-wasm/cypress/e2e/cast-image.cy.ts delete mode 100644 packages/core/typescript/itk-wasm/cypress/e2e/pipeline/pthread-support-available.cy.ts delete mode 100644 packages/core/typescript/itk-wasm/cypress/e2e/pipeline/run-pipeline.cy.ts delete mode 100644 packages/core/typescript/itk-wasm/cypress/e2e/web-worker-pool.cy.ts delete mode 100644 packages/core/typescript/itk-wasm/cypress/support/commands.ts delete mode 100644 packages/core/typescript/itk-wasm/cypress/support/compareImageToBaseline.ts delete mode 100644 packages/core/typescript/itk-wasm/cypress/support/e2e.ts delete mode 100644 packages/core/typescript/itk-wasm/cypress/tsconfig.json create mode 100644 packages/core/typescript/itk-wasm/playwright.config.js create mode 100644 packages/core/typescript/itk-wasm/test/browser/cast-image.spec.js rename packages/core/typescript/itk-wasm/{cypress/e2e/common.ts => test/browser/common.js} (78%) create mode 100644 packages/core/typescript/itk-wasm/test/browser/compare-image-to-baseline.js create mode 100644 packages/core/typescript/itk-wasm/test/browser/pipeline/pthread-support-available.spec.js create mode 100644 packages/core/typescript/itk-wasm/test/browser/pipeline/run-pipeline.spec.js create mode 100644 packages/core/typescript/itk-wasm/test/browser/web-worker-pool.spec.js diff --git a/.github/workflows/cypress.yml b/.github/workflows/cypress.yml index aaeef06aa..5c8529ad0 100644 --- a/.github/workflows/cypress.yml +++ b/.github/workflows/cypress.yml @@ -108,92 +108,3 @@ jobs: name: cypress-screenshots-${{ matrix.package }} path: packages/${{ matrix.package }}/typescript/cypress/screenshots if-no-files-found: ignore - - test-itk-wasm-cypress: - name: itk-wasm browser tests - runs-on: ubuntu-24.04 - - steps: - - name: Checkout - uses: actions/checkout@v4 - - - name: Free Disk Space (Ubuntu) - uses: jlumbroso/free-disk-space@main - with: - large-packages: false - tool-cache: true - - - name: Pull latest Docker images - run: | - ./src/docker/pull.sh --no-debug - - - name: Install - uses: pnpm/action-setup@v4 - - - uses: actions/setup-node@v4 - with: - node-version: '22' - cache: pnpm - cache-dependency-path: ./pnpm-lock.yaml - - - name: Install node, cypress - run: | - pnpm install --frozen-lockfile - pnpx cypress install - - - name: Build itk-wasm - run: | - pnpm run --aggregate-output --filter itk-wasm build - - - name: Build @itk-wasm/demo-app - run: | - pnpm run --aggregate-output --filter '@itk-wasm/demo-app' build - - - name: Build build:gen:typescript - run: | - pnpm run --aggregate-output build:gen:typescript - - - name: Build itk-wasm - run: | - pnpm run --aggregate-output --filter itk-wasm build - # Test deps - pnpm run --aggregate-output --filter "@itk-wasm/demo-app" build - pnpm run --aggregate-output --filter "@itk-wasm/mesh-io" build - pnpm run --aggregate-output --filter "@itk-wasm/transform-io" build - pnpm run --aggregate-output --filter "@itk-wasm/image-io" build - - - name: Test itk-wasm with Chrome - uses: cypress-io/github-action@v6 - with: - browser: chrome - working-directory: packages/core/typescript/itk-wasm - install: false - start: pnpm start - config: video=true - wait-on: 'http://localhost:5180' - wait-on-timeout: 360 - - - uses: actions/upload-artifact@v4 - if: always() - with: - name: cypress-videos - path: packages/core/typescript/itk-wasm/cypress/videos - if-no-files-found: ignore - - #- name: Test with Firefox - #uses: cypress-io/github-action@v6 - #with: - #browser: firefox - #working-directory: packages/core/typescript/itk-wasm - #install: false - #config: video=true - #start: pnpm start - #wait-on: 'http://localhost:5180' - #wait-on-timeout: 360 - - - uses: actions/upload-artifact@v4 - if: failure() - with: - name: cypress-screenshots - path: packages/core/typescript/itk-wasm/cypress/videos - if-no-files-found: ignore diff --git a/.github/workflows/playwright.yml b/.github/workflows/playwright.yml index 212b2d279..199e1d949 100644 --- a/.github/workflows/playwright.yml +++ b/.github/workflows/playwright.yml @@ -75,3 +75,64 @@ jobs: name: playwright-report path: playwright-report/ retention-days: 30 + + test-itk-wasm: + name: itk-wasm browser tests + runs-on: ubuntu-24.04 + + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Free Disk Space (Ubuntu) + uses: jlumbroso/free-disk-space@main + with: + large-packages: false + tool-cache: true + + - name: Pull latest Docker images + run: | + ./src/docker/pull.sh --no-debug + + - name: Install + uses: pnpm/action-setup@v4 + + - uses: actions/setup-node@v4 + with: + node-version: '22' + cache: pnpm + + - name: Install Playwright Browsers + run: pnpx playwright install --with-deps + + - name: Build itk-wasm + run: | + pnpm run --aggregate-output --filter itk-wasm build + + - name: Build @itk-wasm/demo-app + run: | + pnpm run --aggregate-output --filter '@itk-wasm/demo-app' build + + - name: Build build:gen:typescript + run: | + pnpm run --aggregate-output build:gen:typescript + + - name: Build itk-wasm + run: | + pnpm run --aggregate-output --filter itk-wasm build + # Test deps + pnpm run --aggregate-output --filter "@itk-wasm/demo-app" build + pnpm run --aggregate-output --filter "@itk-wasm/mesh-io" build + pnpm run --aggregate-output --filter "@itk-wasm/transform-io" build + pnpm run --aggregate-output --filter "@itk-wasm/image-io" build + + - name: Run Playwright tests + working-directory: ./packages/core/typescript/itk-wasm + run: pnpm run test:browser + + - uses: actions/upload-artifact@v4 + if: ${{ !cancelled() }} + with: + name: playwright-report + path: packages/core/typescript/itk-wasm/playwright-report/ + retention-days: 30 \ No newline at end of file diff --git a/package.json b/package.json index dca62b526..e19e092d6 100644 --- a/package.json +++ b/package.json @@ -35,7 +35,8 @@ "devDependencies": { "@changesets/cli": "^2.27.1", "@commitlint/cli": "^19.3.0", - "@commitlint/config-conventional": "^19.2.2" + "@commitlint/config-conventional": "^19.2.2", + "@playwright/test": "^1.53.0" }, "pnpm": { "overrides": { diff --git a/packages/core/typescript/itk-wasm/.gitignore b/packages/core/typescript/itk-wasm/.gitignore index e74b40ab7..c1493b006 100644 --- a/packages/core/typescript/itk-wasm/.gitignore +++ b/packages/core/typescript/itk-wasm/.gitignore @@ -3,4 +3,5 @@ test/pipelines/wasi-build test/pipelines/typescript test/pipelines/python -cypress/screenshots +playwright-report/ +test-results/ diff --git a/packages/core/typescript/itk-wasm/cypress.config.ts b/packages/core/typescript/itk-wasm/cypress.config.ts deleted file mode 100644 index 0391b1710..000000000 --- a/packages/core/typescript/itk-wasm/cypress.config.ts +++ /dev/null @@ -1,13 +0,0 @@ -import { defineConfig } from "cypress"; - -export default defineConfig({ - projectId: '3ow3bt', - e2e: { - defaultCommandTimeout: 20000, - baseUrl: "http://localhost:5180", - setupNodeEvents(on, config) { - // implement node event listeners here - }, - includeShadowDom: true, // to query into itk-image-detail - }, -}); diff --git a/packages/core/typescript/itk-wasm/cypress/e2e/cast-image.cy.ts b/packages/core/typescript/itk-wasm/cypress/e2e/cast-image.cy.ts deleted file mode 100644 index 96453e15a..000000000 --- a/packages/core/typescript/itk-wasm/cypress/e2e/cast-image.cy.ts +++ /dev/null @@ -1,147 +0,0 @@ -import compareImageToBaseline from "../support/compareImageToBaseline" - -describe('castImage', () => { - beforeEach(() => { - cy.visit('/') - }) - - - it('copies the input when no options are passed', () => { - cy.window().then(async (win) => { - const itk = win.itk - - const inputImageType = new itk.ImageType() - const inputImage = new itk.Image(inputImageType) - inputImage.size = [256, 256] - inputImage.data = new Uint8Array(256*256) - inputImage.data.fill(7) - inputImage.origin = [3.0, 4.0] - inputImage.spacing = [9.0, 4.0] - inputImage.direction[0] = -1.0 - - const outputImage = itk.castImage(inputImage, {}) - - compareImageToBaseline(itk, outputImage, inputImage) - }) - }) - - - it('casts to the specified pixel type', () => { - cy.window().then(async (win) => { - const itk = win.itk - - const inputImageType = new itk.ImageType() - const inputImage = new itk.Image(inputImageType) - inputImage.size = [256, 256] - inputImage.data = new Uint8Array(256*256) - inputImage.data.fill(7) - - const outputImage = itk.castImage(inputImage, { pixelType: itk.PixelTypes.CovariantVector }) - - const baseline = inputImage - baseline.imageType.pixelType = itk.PixelTypes.CovariantVector - - compareImageToBaseline(itk, outputImage, baseline) - }) - }) - - - it('throws an error when casting a multi-component image to a scalar image', () => { - cy.window().then(async (win) => { - const itk = win.itk - - const inputImageType = new itk.ImageType(2, itk.IntTypes.UInt8, itk.PixelTypes.Complex, 2) - const inputImage = new itk.Image(inputImageType) - inputImage.size = [256, 256] - inputImage.data = new Uint8Array(256*256 * 2) - inputImage.data.fill(7) - - expect(() => { - itk.castImage(inputImage, { pixelType: itk.PixelTypes.Scalar }) - }).to.throw() - }) - }) - - it('casts to another TypedArray component type', () => { - cy.window().then(async (win) => { - const itk = win.itk - - const inputImageType = new itk.ImageType() - const inputImage = new itk.Image(inputImageType) - inputImage.size = [256, 256] - inputImage.data = new Uint8Array(256*256) - inputImage.data.fill(7) - - const outputImage = itk.castImage(inputImage, { componentType: itk.FloatTypes.Float32 }) - - const baseline = inputImage - baseline.imageType.componentType = itk.FloatTypes.Float32 - baseline.data = new Float32Array(baseline.data) - - compareImageToBaseline(itk, outputImage, baseline) - }) - }) - - it('casts to a 64-bit integer component type', () => { - cy.window().then(async (win) => { - const itk = win.itk - - const inputImageType = new itk.ImageType() - const inputImage = new itk.Image(inputImageType) - inputImage.size = [256, 256] - inputImage.data = new Uint8Array(256*256) - inputImage.data.fill(7) - - const outputImage = itk.castImage(inputImage, { componentType: itk.IntTypes.UInt64 }) - - const baseline = inputImage - baseline.imageType.componentType = itk.IntTypes.UInt64 - baseline.data = new BigUint64Array(baseline.data.length) - baseline.data.fill(7n) - - compareImageToBaseline(itk, outputImage, baseline) - }) - }) - - it('casts from 64-bit to TypedArray component type', () => { - cy.window().then(async (win) => { - const itk = win.itk - - const inputImageType = new itk.ImageType(2, itk.IntTypes.UInt64, itk.PixelTypes.Scalar, 1) - const inputImage = new itk.Image(inputImageType) - inputImage.size = [256, 256] - inputImage.data = new BigUint64Array(256*256) - inputImage.data.fill(7n) - - const outputImage = itk.castImage(inputImage, { componentType: itk.FloatTypes.Float32 }) - - const baseline = inputImage - baseline.imageType.componentType = itk.FloatTypes.Float32 - baseline.data = new Float32Array(baseline.data.length) - baseline.data.fill(7) - - compareImageToBaseline(itk, outputImage, baseline) - }) - }) - - it('casts from 64-bit to another 64-bit integer component type', () => { - cy.window().then(async (win) => { - const itk = win.itk - - const inputImageType = new itk.ImageType() - const inputImage = new itk.Image(inputImageType) - inputImage.size = [256, 256] - inputImage.data = new BigInt64Array(256*256) - inputImage.data.fill(7n) - - const outputImage = itk.castImage(inputImage, { componentType: itk.IntTypes.UInt64 }) - - const baseline = inputImage - baseline.imageType.componentType = itk.IntTypes.UInt64 - baseline.data = new BigUint64Array(baseline.data.length) - baseline.data.fill(7n) - - compareImageToBaseline(itk, outputImage, baseline) - }) - }) -}) diff --git a/packages/core/typescript/itk-wasm/cypress/e2e/pipeline/pthread-support-available.cy.ts b/packages/core/typescript/itk-wasm/cypress/e2e/pipeline/pthread-support-available.cy.ts deleted file mode 100644 index 57c332046..000000000 --- a/packages/core/typescript/itk-wasm/cypress/e2e/pipeline/pthread-support-available.cy.ts +++ /dev/null @@ -1,21 +0,0 @@ -import { demoServer, pipelineBaseUrl, pipelineWorkerUrl } from '../common' - -describe('pthreadSupportAvailable', () => { - beforeEach(() => { - cy.visit(demoServer) - cy.window().then(async (win) => { - const itk = win.itk - itk.setPipelineWorkerUrl(pipelineWorkerUrl) - itk.setPipelinesBaseUrl(pipelineBaseUrl) - }) - }) - - it('reports whether pthread support is available', () => { - cy.window().then(async (win) => { - const itk = win.itk - - const pthreadSupportAvailable = itk.pthreadSupportAvailable() - expect(pthreadSupportAvailable).to.equal(false) - }) - }) -}) diff --git a/packages/core/typescript/itk-wasm/cypress/e2e/pipeline/run-pipeline.cy.ts b/packages/core/typescript/itk-wasm/cypress/e2e/pipeline/run-pipeline.cy.ts deleted file mode 100644 index 460fe8ebc..000000000 --- a/packages/core/typescript/itk-wasm/cypress/e2e/pipeline/run-pipeline.cy.ts +++ /dev/null @@ -1,315 +0,0 @@ -import { readIwi, readIwm, demoServer, pipelineBaseUrl, pipelineWorkerUrl } from "../common" - -describe('runPipeline', () => { - beforeEach(() => { - cy.visit(demoServer) - cy.window().then(async (win) => { - const itk = win.itk - itk.setPipelineWorkerUrl(pipelineWorkerUrl) - itk.setPipelinesBaseUrl(pipelineBaseUrl) - }) - }) - - it('captures stdout and stderr', () => { - cy.window().then(async (win) => { - const itk = win.itk - - const args = [] - const outputs = null - const inputs = null - const stdoutStderrPath = 'stdout-stderr-test' - const { webWorker, returnValue, stdout, stderr } = await itk.runPipeline(stdoutStderrPath, args, outputs, inputs) - }) - }) - - it('fetches Wasm files from a custom URL', () => { - cy.window().then(async (win) => { - const itk = win.itk - - const args = [] - const outputs = null - const inputs = null - const stdoutStderrPath = 'stdout-stderr-test' - const { webWorker, returnValue, stdout, stderr } = await itk.runPipeline(stdoutStderrPath, args, outputs, inputs, { pipelineBaseUrl, pipelineWorkerUrl }) - }) - }) - - it('fetches Wasm files from a custom URL and query params', () => { - cy.window().then(async (win) => { - const itk = win.itk - - const args = [] - const outputs = null - const inputs = null - const stdoutStderrPath = 'stdout-stderr-test' - const { webWorker, returnValue, stdout, stderr } = await itk.runPipeline(stdoutStderrPath, args, outputs, inputs, { pipelineBaseUrl, pipelineWorkerUrl, pipelineQueryParams: {key: 'value'} }) - }) - }) - - it('fetches Wasm files from a custom pipelineBaseUrl string', () => { - cy.window().then(async (win) => { - const itk = win.itk - const pipelineBaseUrl = new URL('/pipelines', demoServer).href - - const args = [] - const outputs = null - const inputs = null - const stdoutStderrPath = 'stdout-stderr-test' - const { webWorker, returnValue, stdout, stderr } = await itk.runPipeline(stdoutStderrPath, args, outputs, inputs, { pipelineBaseUrl }) - }) - }) - - it('fetches the pipeline web worker from a custom pipelineWorkerUrl string', () => { - cy.window().then(async (win) => { - const itk = win.itk - const pipelineWorkerUrl = new URL('/itk-wasm-pipeline.worker.js', demoServer).href - - const args = [] - const outputs = null - const inputs = null - const stdoutStderrPath = 'stdout-stderr-test' - const { webWorker, returnValue, stdout, stderr } = await itk.runPipeline(stdoutStderrPath, args, outputs, inputs, { pipelineWorkerUrl }) - }) - }) - - it('uses a web worker created explicitly beforehand', () => { - cy.window().then(async (win) => { - const itk = win.itk - const pipelineWorkerUrl = new URL('/itk-wasm-pipeline.worker.js', demoServer).href - const webWorker = await itk.createWebWorker(pipelineWorkerUrl) - - const args = [] - const outputs = null - const inputs = null - const stdoutStderrPath = 'stdout-stderr-test' - const { returnValue, stdout, stderr } = await itk.runPipeline(stdoutStderrPath, args, outputs, inputs, { webWorker }) - }) - }) - - it('re-uses a WebWorker', () => { - cy.window().then(async (win) => { - const itk = win.itk - - const args = [] - const outputs = null - const inputs = null - const stdoutStderrPath = 'stdout-stderr-test' - const { webWorker } = await itk.runPipeline(stdoutStderrPath, args, outputs, inputs) - const { returnValue, stdout, stderr } = await itk.runPipeline(stdoutStderrPath, args, outputs, inputs, { webWorker }) - expect(typeof webWorker.terminated).to.equal('boolean') - expect(webWorker.terminated).to.equal(false) - webWorker.terminate() - expect(webWorker.terminated).to.equal(true) - expect(returnValue, 'returnValue').to.equal(0) - expect(stdout, 'stdout').to.equal(`I’m writing my code, -But I do not realize, -Hours have gone by. -`) - expect(stderr, 'stderr').to.equal(`The modem humming -Code rapidly compiling. -Click. Perfect success. -`) - }) - }) - - - it('runs a pipeline on the main thread with an absolute URL', () => { - cy.window().then(async (win) => { - const itk = win.itk - - const args = [] - const outputs = null - const inputs = null - const absoluteURL = new URL('/pipelines/stdout-stderr-test', document.location) - const { returnValue, stdout, stderr } = await itk.runPipeline(absoluteURL, args, outputs, inputs, { webWorker: false }) - expect(returnValue, 'returnValue').to.equal(0) - expect(stdout, 'stdout').to.equal(`I’m writing my code, -But I do not realize, -Hours have gone by. -`) - expect(stderr, 'stderr').to.equal(`The modem humming -Code rapidly compiling. -Click. Perfect success. -`) - }) - }) - - it('uses input and output text and binary data via memory io', () => { - cy.window().then(async (win) => { - const itk = win.itk - - const pipelinePath = 'input-output-files-test' - const args = ['--memory-io', - '--input-text-stream', '0', - '--input-binary-stream', '1', - '0', - '1' - ] - const desiredOutputs = [ - { type: itk.InterfaceTypes.TextStream }, - { type: itk.InterfaceTypes.BinaryStream } - ] - const inputs = [ - { type: itk.InterfaceTypes.TextStream, data: { data: 'The answer is 42.' } }, - { type: itk.InterfaceTypes.BinaryStream, data: { data: new Uint8Array([222, 173, 190, 239]) } } - ] - const { stdout, stderr, outputs, webWorker } = await itk.runPipeline(pipelinePath, args, desiredOutputs, inputs) - webWorker.terminate() - expect(outputs[0].type, 'text output type').to.equal(itk.InterfaceTypes.TextStream) - expect(outputs[0].data.data, 'text output data').to.equal('The answer is 42.') - expect(outputs[1].type, 'binary output type').to.equal(itk.InterfaceTypes.BinaryStream) - expect(outputs[1].data.data[0], 'binary output data[0]').to.equal(222) - expect(outputs[1].data.data[1], 'binary output data[1]').to.equal(173) - expect(outputs[1].data.data[2], 'binary output data[2]').to.equal(190) - expect(outputs[1].data.data[3], 'binary output data[3]').to.equal(239) - expect(stdout, 'stdout').to.equal(`Input text: The answer is 42. -`) - expect(stderr, 'stderr').to.equal(`Input binary: ffffffdeffffffadffffffbeffffffef -`) - }) - }) - - - it('runs on the main thread when webWorker option is false', () => { - cy.window().then(async (win) => { - const itk = win.itk - - const pipelinePath = 'input-output-files-test' - const args = ['--memory-io', - '--input-text-stream', '0', - '--input-binary-stream', '1', - '0', - '1' - ] - const desiredOutputs = [ - { type: itk.InterfaceTypes.TextStream }, - { type: itk.InterfaceTypes.BinaryStream } - ] - const inputs = [ - { type: itk.InterfaceTypes.TextStream, data: { data: 'The answer is 42.' } }, - { type: itk.InterfaceTypes.BinaryStream, data: { data: new Uint8Array([222, 173, 190, 239]) } } - ] - const { stdout, stderr, outputs } = await itk.runPipeline(pipelinePath, args, desiredOutputs, inputs, { webWorker: false }) - expect(outputs[0].type, 'text output type').to.equal(itk.InterfaceTypes.TextStream) - expect(outputs[0].data.data, 'text output data').to.equal('The answer is 42.') - expect(outputs[1].type, 'binary output type').to.equal(itk.InterfaceTypes.BinaryStream) - expect(outputs[1].data.data[0], 'binary output data[0]').to.equal(222) - expect(outputs[1].data.data[1], 'binary output data[1]').to.equal(173) - expect(outputs[1].data.data[2], 'binary output data[2]').to.equal(190) - expect(outputs[1].data.data[3], 'binary output data[3]').to.equal(239) - expect(stdout, 'stdout').to.equal(`Input text: The answer is 42. -`) - expect(stderr, 'stderr').to.equal(`Input binary: ffffffdeffffffadffffffbeffffffef -`) - }) - }) - - it('writes and reads itk.Image\'s', () => { - cy.window().then(async (win) => { - const itk = win.itk - - const verifyImage = (image) => { - expect(image.imageType.dimension, 'dimension').to.equal(2) - expect(image.imageType.componentType, 'componentType').to.equal(itk.IntTypes.UInt8) - expect(image.imageType.pixelType, 'pixelType').to.equal(itk.PixelTypes.Scalar) - expect(image.imageType.components, 'components').to.equal(1) - expect(image.origin, 'origin').to.deep.equal([0.0, 0.0]) - expect(image.spacing, 'spacing').to.deep.equal([1.0, 1.0]) - expect(image.size, 'size').to.deep.equal([256, 256]) - expect(image.data.byteLength, 'data.byteLength').to.equal(65536) - } - - const cthead1BaseUrl = new URL('/data/cthead1.iwi/', demoServer).href - const image = await readIwi(cthead1BaseUrl) - - const pipelinePath = 'median-filter-test' - const args = [ - '0', - '0', - '--radius', '4', - '--memory-io'] - const desiredOutputs = [ - { type: itk.InterfaceTypes.Image } - ] - const inputs = [ - { type: itk.InterfaceTypes.Image, data: image } - ] - const { webWorker, outputs } = await itk.runPipeline(pipelinePath, args, desiredOutputs, inputs) - webWorker.terminate() - verifyImage(outputs[0].data) - }) - }) - - it('runs twice without a detached buffer', () => { - cy.window().then(async (win) => { - const itk = win.itk - - const verifyImage = (image) => { - expect(image.imageType.dimension, 'dimension').to.equal(2) - expect(image.imageType.componentType, 'componentType').to.equal(itk.IntTypes.UInt8) - expect(image.imageType.pixelType, 'pixelType').to.equal(itk.PixelTypes.Scalar) - expect(image.imageType.components, 'components').to.equal(1) - expect(image.origin, 'origin').to.deep.equal([0.0, 0.0]) - expect(image.spacing, 'spacing').to.deep.equal([1.0, 1.0]) - expect(image.size, 'size').to.deep.equal([256, 256]) - expect(image.data.byteLength, 'data.byteLength').to.equal(65536) - } - - const cthead1BaseUrl = new URL('/data/cthead1.iwi/', demoServer).href - const image = await readIwi(cthead1BaseUrl) - - const pipelinePath = 'median-filter-test' - const args = [ - '0', - '0', - '--radius', '4', - '--memory-io'] - const desiredOutputs = [ - { type: itk.InterfaceTypes.Image } - ] - const inputs = [ - { type: itk.InterfaceTypes.Image, data: image } - ] - // const options = { noCopy: true } // failure expected - const options = { noCopy: false } - const { webWorker } = await itk.runPipeline(pipelinePath, args, desiredOutputs, inputs, options) - options.webWorker = webWorker - const { outputs } = await itk.runPipeline(pipelinePath, args, desiredOutputs, inputs, options) - webWorker.terminate() - - verifyImage(outputs[0].data) - }) - }) - - it('runPipeline writes and reads an itk.Mesh via memory io', () => { - cy.window().then(async (win) => { - const itk = win.itk - - const verifyMesh = (mesh) => { - expect(mesh.meshType.dimension, 'dimension').to.equal(3) - expect(mesh.meshType.pointComponentType, 'pointComponentType').to.equal(itk.FloatTypes.Float32) - expect(mesh.meshType.cellComponentType, 'cellComponentType').to.equal(itk.IntTypes.UInt32) - expect(mesh.meshType.pointPixelType, 'pointPixelType').to.equal(itk.PixelTypes.Scalar) - expect(mesh.meshType.cellPixelType, 'cellPixelType').to.equal(itk.PixelTypes.Scalar) - expect(mesh.numberOfPoints, 'numberOfPoints').to.equal(2903) - expect(mesh.numberOfCells, 'numberOfCells').to.equal(3263) - } - - const cowBaseUrl = new URL('/data/cow.iwm/', demoServer).href - const mesh = await readIwm(cowBaseUrl) - - const pipelinePath = 'mesh-read-write-test' - const args = ['0', '0', '--memory-io'] - const desiredOutputs = [ - { type: itk.InterfaceTypes.Mesh } - ] - const inputs = [ - { type: itk.InterfaceTypes.Mesh, data: mesh } - ] - const { webWorker, outputs } = await itk.runPipeline(pipelinePath, args, desiredOutputs, inputs) - webWorker.terminate() - verifyMesh(outputs[0].data) - }) - }) - -}) diff --git a/packages/core/typescript/itk-wasm/cypress/e2e/web-worker-pool.cy.ts b/packages/core/typescript/itk-wasm/cypress/e2e/web-worker-pool.cy.ts deleted file mode 100644 index c76bc508e..000000000 --- a/packages/core/typescript/itk-wasm/cypress/e2e/web-worker-pool.cy.ts +++ /dev/null @@ -1,58 +0,0 @@ -import { readIwi, demoServer, pipelineBaseUrl, pipelineWorkerUrl } from "./common" -import compareImageToBaseline from "../support/compareImageToBaseline" - -describe('WebWorkerPool', () => { - beforeEach(() => { - cy.visit(demoServer) - cy.window().then(async (win) => { - const itk = win.itk - itk.setPipelineWorkerUrl(pipelineWorkerUrl) - itk.setPipelinesBaseUrl(pipelineBaseUrl) - }) - }) - - it('runs and reports progress', () => { - cy.window().then(async (win) => { - const itk = win.itk - const progressUpdates = [] - let reportedTotalSplits = null - function progressLogger (split, totalSplits) { - progressUpdates.push(split) - reportedTotalSplits = totalSplits - } - - const poolSize = 2 - const maxTotalSplits = 4 - const workerPool = new itk.WorkerPool(poolSize, itk.runPipeline) - - const cthead1BaseUrl = new URL('/data/cthead1.iwi/', demoServer).href - const image = await readIwi(cthead1BaseUrl) - - const taskArgsArray = [] - for (let index = 0; index < maxTotalSplits; index++) { - const pipelinePath = 'median-filter-test' - const args = ['--memory-io', '0', '0', '--radius', '4', '-m', '' + maxTotalSplits, '-s', '' + index] - const desiredOutputs = [ - { type: itk.InterfaceTypes.Image } - ] - const data = itk.imageSharedBufferOrCopy(image) - const inputs = [ - { type: itk.InterfaceTypes.Image, data } - ] - taskArgsArray.push([pipelinePath, args, desiredOutputs, inputs, {}]) - } - - const results = await workerPool.runTasks(taskArgsArray, progressLogger).promise - workerPool.terminateWorkers() - - expect(reportedTotalSplits, 'reported total splits', maxTotalSplits) - const imageSplits = results.map(({ outputs }) => outputs[0].data) - const stackedImage = itk.stackImages(imageSplits) - - const baselineBaseUrl = new URL('/data/web-worker-pool-baseline.iwi/', demoServer).href - const baselineImage = await readIwi(baselineBaseUrl) - - compareImageToBaseline(itk, stackedImage, baselineImage) - }) - }) -}) diff --git a/packages/core/typescript/itk-wasm/cypress/support/commands.ts b/packages/core/typescript/itk-wasm/cypress/support/commands.ts deleted file mode 100644 index 698b01a42..000000000 --- a/packages/core/typescript/itk-wasm/cypress/support/commands.ts +++ /dev/null @@ -1,37 +0,0 @@ -/// -// *********************************************** -// This example commands.ts shows you how to -// create various custom commands and overwrite -// existing commands. -// -// For more comprehensive examples of custom -// commands please read more here: -// https://on.cypress.io/custom-commands -// *********************************************** -// -// -// -- This is a parent command -- -// Cypress.Commands.add('login', (email, password) => { ... }) -// -// -// -- This is a child command -- -// Cypress.Commands.add('drag', { prevSubject: 'element'}, (subject, options) => { ... }) -// -// -// -- This is a dual command -- -// Cypress.Commands.add('dismiss', { prevSubject: 'optional'}, (subject, options) => { ... }) -// -// -// -- This will overwrite an existing command -- -// Cypress.Commands.overwrite('visit', (originalFn, url, options) => { ... }) -// -// declare global { -// namespace Cypress { -// interface Chainable { -// login(email: string, password: string): Chainable -// drag(subject: string, options?: Partial): Chainable -// dismiss(subject: string, options?: Partial): Chainable -// visit(originalFn: CommandOriginalFn, url: string, options: Partial): Chainable -// } -// } -// } \ No newline at end of file diff --git a/packages/core/typescript/itk-wasm/cypress/support/compareImageToBaseline.ts b/packages/core/typescript/itk-wasm/cypress/support/compareImageToBaseline.ts deleted file mode 100644 index 757d24a6c..000000000 --- a/packages/core/typescript/itk-wasm/cypress/support/compareImageToBaseline.ts +++ /dev/null @@ -1,9 +0,0 @@ -const compareImageToBaseline = (itk, testImage, baselineImage) => { - expect(testImage.imageType, 'imageType').to.deep.equal(baselineImage.imageType) - expect(testImage.origin, 'origin').to.deep.equal(baselineImage.origin) - expect(testImage.spacing, 'spacing').to.deep.equal(baselineImage.spacing) - expect(testImage.size, 'size').to.deep.equal(baselineImage.size) - expect(testImage.data, 'data').to.deep.equal(baselineImage.data) -} - -export default compareImageToBaseline \ No newline at end of file diff --git a/packages/core/typescript/itk-wasm/cypress/support/e2e.ts b/packages/core/typescript/itk-wasm/cypress/support/e2e.ts deleted file mode 100644 index f80f74f8e..000000000 --- a/packages/core/typescript/itk-wasm/cypress/support/e2e.ts +++ /dev/null @@ -1,20 +0,0 @@ -// *********************************************************** -// This example support/e2e.ts is processed and -// loaded automatically before your test files. -// -// This is a great place to put global configuration and -// behavior that modifies Cypress. -// -// You can change the location of this file or turn off -// automatically serving support files with the -// 'supportFile' configuration option. -// -// You can read more here: -// https://on.cypress.io/configuration -// *********************************************************** - -// Import commands.js using ES2015 syntax: -import './commands' - -// Alternatively you can use CommonJS syntax: -// require('./commands') \ No newline at end of file diff --git a/packages/core/typescript/itk-wasm/cypress/tsconfig.json b/packages/core/typescript/itk-wasm/cypress/tsconfig.json deleted file mode 100644 index 212405809..000000000 --- a/packages/core/typescript/itk-wasm/cypress/tsconfig.json +++ /dev/null @@ -1,12 +0,0 @@ -{ - "extends": "../tsconfig.json", - "include": [ - "**/*.ts" - ], - "compilerOptions": { - "noEmit": false, - "sourceMap": false, - "inlineSourceMap": true, - "types": ["cypress"] - }, -} diff --git a/packages/core/typescript/itk-wasm/package.json b/packages/core/typescript/itk-wasm/package.json index 9c628c3f0..f63ff6664 100644 --- a/packages/core/typescript/itk-wasm/package.json +++ b/packages/core/typescript/itk-wasm/package.json @@ -27,18 +27,11 @@ "build:workerBundle": "esbuild --bundle --format=esm --outfile=./dist/pipeline/web-workers/bundles/itk-wasm-pipeline.worker.js ./dist/pipeline/web-workers/itk-wasm-pipeline.worker.js", "build:workerBundleForTesting": "esbuild --bundle --format=esm --outfile=./test/pipelines/typescript/test/browser/demo-app/public/itk-wasm-pipeline.worker.js ./dist/pipeline/web-workers/itk-wasm-pipeline.worker.js && shx cp -r ./test/data/ ./test/pipelines/typescript/test/browser/demo-app/public/", "build:workerMinBundle": "esbuild --minify --bundle --format=esm --outfile=./dist/pipeline/web-workers/bundles/itk-wasm-pipeline.min.worker.js ./dist/pipeline/web-workers/itk-wasm-pipeline.worker.js", - "cypress:open": "pnpm exec cypress open", - "cypress:run": "pnpm exec cypress run --config defaultCommandTimeout=8000", - "cypress:install": "pnpm exec cypress install", - "cypress:runChrome": "pnpm exec cypress run --config defaultCommandTimeout=8000 --browser chrome", - "cypress:runFirefox": "pnpm exec cypress run --config defaultCommandTimeout=8000 --browser firefox", - "cypress:runFirefox:ci": "pnpm cypress:install && pnpm exec cypress run --config defaultCommandTimeout=8000 --browser firefox", "start": "pnpm build:workerBundleForTesting && cd test/pipelines/typescript && pnpm build && pnpm start", "test:wasi": "pnpm test:buildTestPipelines:wasi && pnpm test:runTestPipelines && pnpm test:bindgenTestPipelines:python", - "test": "pnpm test:lint && pnpm test:testPipelines && pnpm test:node && pnpm test:bindgenTestPipelines:python && pnpm test:browser:chrome && pnpm test:browser:firefox", + "test": "pnpm test:lint && pnpm test:testPipelines && pnpm test:node && pnpm test:bindgenTestPipelines:python && pnpm test:browser", "test:lint": "ts-standard --fix \"src/**/*.ts\" && standard --fix \"test/node/**/*.js\"", "test:node": "ava test/node/**/*.js", - "test:browser": "pnpm run test:browser:chrome && pnpm run test:browser:firefox", "test:testPipelines": "pnpm test:buildTestPipelines:emscripten && pnpm test:buildTestPipelines:wasi && pnpm test:runTestPipelines", "test:buildTestPipelines:emscripten:debug": "node src/itk-wasm-cli.js -i quay.io/itkwasm/emscripten:latest-debug -b emscripten-build -s ./test/pipelines build -- -DCMAKE_BUILD_TYPE=Debug", "test:buildTestPipelines:emscripten": "node src/itk-wasm-cli.js -i quay.io/itkwasm/emscripten:latest -b emscripten-build -s ./test/pipelines build", @@ -49,11 +42,9 @@ "test:runTestPipelines": "node src/itk-wasm-cli.js -i quay.io/itkwasm/wasi:latest -b wasi-build -s ./test/pipelines run -r wasmtime stdout-stderr-pipeline/stdout-stderr-test.wasi.wasm", "test:bindgenTestPipelines:typescript": "node src/itk-wasm-cli.js -i quay.io/itkwasm/emscripten:latest -b emscripten-build -s ./test/pipelines/ bindgen --package-version 1.0.0 --package-name test-pipelines --package-description \"Exercise interface types for bindgen\"", "test:bindgenTestPipelines:python": "node src/itk-wasm-cli.js -i quay.io/itkwasm/wasi:latest -b wasi-build -s ./test/pipelines/ bindgen --interface python --package-version 1.0.0 --package-name test-pipelines --package-description \"Exercise interface types for bindgen\"", - "test:browser:debug": "start-server-and-test start http-get://localhost:5180 cypress:open", - "test:cypress": "start-server-and-test start http-get://localhost:5180 cypress:run", - "test:browser:chrome": "start-server-and-test start http-get://localhost:5180 cypress:runChrome", - "test:browser:firefox:ci": "start-server-and-test start http-get://localhost:5180 cypress:runFirefox:ci", - "test:browser:firefox": "start-server-and-test start http-get://localhost:5180 cypress:runFirefox", + "test:browser": "playwright test", + "test:browser:ui": "playwright test --ui", + "test:browser:debug": "playwright test --debug", "prepublishOnly": "pnpm build:tsc && node ./src/update-versions.cjs && pnpm build:workerBundle && pnpm build:workerMinBundle && pnpm build:bundle && pnpm build:minBundle", "clean": "git clean -fdx" }, @@ -78,15 +69,14 @@ }, "homepage": "https://wasm.itk.org/", "devDependencies": { - "@types/node": "^20.10.3", + "@playwright/test": "^1.53.0", + "@types/node": "^22.13.13", "ava": "^5.3.1", - "cypress": "^13.7.3", "esbuild": "^0.25.0", "prettier": "^3.2.5", "prettier-config-standard": "^7.0.0", "shx": "^0.3.4", "standard": "^17.1.0", - "start-server-and-test": "^2.0.3", "ts-standard": "^12.0.2", "typescript": "^5.3.2" }, diff --git a/packages/core/typescript/itk-wasm/playwright.config.js b/packages/core/typescript/itk-wasm/playwright.config.js new file mode 100644 index 000000000..76c95b398 --- /dev/null +++ b/packages/core/typescript/itk-wasm/playwright.config.js @@ -0,0 +1,81 @@ +// @ts-check +import { defineConfig, devices } from '@playwright/test' + +/** + * Read environment variables from file. + * https://github.com/motdotla/dotenv + */ +// import dotenv from 'dotenv'; +// import path from 'path'; +// dotenv.config({ path: path.resolve(__dirname, '.env') }); + +/** + * @see https://playwright.dev/docs/test-configuration + */ +export default defineConfig({ + testDir: './test/browser', + testMatch: '**/*.spec.js', + /* Run tests in files in parallel */ + fullyParallel: true, + /* Fail the build on CI if you accidentally left test.only in the source code. */ + forbidOnly: !!process.env.CI, + /* Retry on CI only */ + retries: process.env.CI ? 2 : 0, + /* Opt out of parallel tests on CI. */ + workers: process.env.CI ? 1 : undefined, + /* Reporter to use. See https://playwright.dev/docs/test-reporters */ + reporter: 'html', + /* Shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions. */ + use: { + /* Base URL to use in actions like `await page.goto('/')`. */ + baseURL: 'http://localhost:5180', + + /* Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer */ + trace: 'on-first-retry' + }, + + /* Configure projects for major browsers */ + projects: [ + { + name: 'chromium', + use: { ...devices['Desktop Chrome'] } + }, + + { + name: 'firefox', + use: { ...devices['Desktop Firefox'] } + }, + + { + name: 'webkit', + use: { ...devices['Desktop Safari'] } + } + + /* Test against mobile viewports. */ + // { + // name: 'Mobile Chrome', + // use: { ...devices['Pixel 5'] }, + // }, + // { + // name: 'Mobile Safari', + // use: { ...devices['iPhone 12'] }, + // }, + + /* Test against branded browsers. */ + // { + // name: 'Microsoft Edge', + // use: { ...devices['Desktop Edge'], channel: 'msedge' }, + // }, + // { + // name: 'Google Chrome', + // use: { ...devices['Desktop Chrome'], channel: 'chrome' }, + // }, + ], + + /* Run your local dev server before starting the tests */ + webServer: { + command: 'pnpm run start', + url: 'http://localhost:5180', + reuseExistingServer: !process.env.CI + } +}) diff --git a/packages/core/typescript/itk-wasm/test/browser/cast-image.spec.js b/packages/core/typescript/itk-wasm/test/browser/cast-image.spec.js new file mode 100644 index 000000000..67b413927 --- /dev/null +++ b/packages/core/typescript/itk-wasm/test/browser/cast-image.spec.js @@ -0,0 +1,199 @@ +import { test, expect } from '@playwright/test' +import compareImageToBaseline from './compare-image-to-baseline.js' + +test.describe('castImage', () => { + test.beforeEach(async ({ page }) => { + await page.goto('/') + }) + + test('copies the input when no options are passed', async ({ page }) => { + const { outputImage, inputImage: baseline } = await page.evaluate(() => { + const itk = window.itk + + const inputImageType = new itk.ImageType() + const inputImage = new itk.Image(inputImageType) + inputImage.size = [256, 256] + inputImage.data = new Uint8Array(256 * 256) + inputImage.data.fill(7) + inputImage.origin = [3.0, 4.0] + inputImage.spacing = [9.0, 4.0] + inputImage.direction.set([-1.0, 0.0, 0.0, -1.0]) // Assuming 2D, ensure full direction matrix + + const outputImage = itk.castImage(inputImage, {}) + + // Return both for comparison + return { + outputImage: JSON.parse(JSON.stringify(outputImage)), + inputImage: JSON.parse(JSON.stringify(inputImage)) + } + }) + + compareImageToBaseline(outputImage, baseline) + }) + + test('casts to the specified pixel type', async ({ page }) => { + const { outputImage, baseline } = await page.evaluate(() => { + const itk = window.itk + + const inputImageType = new itk.ImageType() + const inputImage = new itk.Image(inputImageType) + inputImage.size = [256, 256] + inputImage.data = new Uint8Array(256 * 256) + inputImage.data.fill(7) + // Ensure other properties are set if compareImageToBaseline checks them + inputImage.origin = [0.0, 0.0] + inputImage.spacing = [1.0, 1.0] + inputImage.direction.set([1.0, 0.0, 0.0, 1.0]) + + const outputImage = itk.castImage(inputImage, { + pixelType: itk.PixelTypes.CovariantVector + }) + + const baseline = JSON.parse(JSON.stringify(inputImage)) // Deep copy + baseline.imageType.pixelType = itk.PixelTypes.CovariantVector + // Data itself doesn't change type here, just the interpretation if components > 1 + // If compareImageToBaseline relies on data type, this might need adjustment + // For CovariantVector with 1 component, data remains the same. + + return { outputImage: JSON.parse(JSON.stringify(outputImage)), baseline } + }) + + compareImageToBaseline(outputImage, baseline) + }) + + test('throws an error when casting a multi-component image to a scalar image', async ({ + page + }) => { + const errorOccurred = await page.evaluate(() => { + const itk = window.itk + + const inputImageType = new itk.ImageType( + 2, + itk.IntTypes.UInt8, + itk.PixelTypes.Complex, + 2 + ) + const inputImage = new itk.Image(inputImageType) + inputImage.size = [256, 256] + inputImage.data = new Uint8Array(256 * 256 * 2 * 2) // Complex has 2 components, and components=2 + inputImage.data.fill(7) + // Ensure other properties are set + inputImage.origin = [0.0, 0.0] + inputImage.spacing = [1.0, 1.0] + inputImage.direction.set([1.0, 0.0, 0.0, 1.0]) + + try { + itk.castImage(inputImage, { pixelType: itk.PixelTypes.Scalar }) + return false // Should not reach here + } catch (e) { + return true + } + }) + expect(errorOccurred).toBe(true) + }) + + test('casts to another TypedArray component type', async ({ page }) => { + const { outputImage, baseline } = await page.evaluate(() => { + const itk = window.itk + + const inputImageType = new itk.ImageType() + const inputImage = new itk.Image(inputImageType) + inputImage.size = [256, 256] + inputImage.data = new Uint8Array(256 * 256) + inputImage.data.fill(7) + + const outputImage = itk.castImage(inputImage, { + componentType: itk.FloatTypes.Float32 + }) + + const baseline = inputImage + baseline.imageType.componentType = itk.FloatTypes.Float32 + baseline.data = new Float32Array(baseline.data) + + return { outputImage, baseline } + }) + + compareImageToBaseline(outputImage, baseline) + }) + + test('casts to a 64-bit integer component type', async ({ page }) => { + const { outputImage, baseline } = await page.evaluate(() => { + const itk = window.itk + + const inputImageType = new itk.ImageType() + const inputImage = new itk.Image(inputImageType) + inputImage.size = [256, 256] + inputImage.data = new Uint8Array(256 * 256) + inputImage.data.fill(7) + + const outputImage = itk.castImage(inputImage, { + componentType: itk.IntTypes.UInt64 + }) + + const baseline = inputImage + baseline.imageType.componentType = itk.IntTypes.UInt64 + baseline.data = new BigUint64Array(baseline.data.length) + baseline.data.fill(7n) + + return { outputImage, baseline } + }) + + compareImageToBaseline(outputImage, baseline) + }) + + test('casts from 64-bit to TypedArray component type', async ({ page }) => { + const { outputImage, baseline } = await page.evaluate(() => { + const itk = window.itk + + const inputImageType = new itk.ImageType( + 2, + itk.IntTypes.UInt64, + itk.PixelTypes.Scalar, + 1 + ) + const inputImage = new itk.Image(inputImageType) + inputImage.size = [256, 256] + inputImage.data = new BigUint64Array(256 * 256) + inputImage.data.fill(7n) + + const outputImage = itk.castImage(inputImage, { + componentType: itk.FloatTypes.Float32 + }) + + const baseline = inputImage + baseline.imageType.componentType = itk.FloatTypes.Float32 + baseline.data = new Float32Array(baseline.data.length) + baseline.data.fill(7) + + return { outputImage, baseline } + }) + + compareImageToBaseline(outputImage, baseline) + }) + + test('casts from 64-bit to another 64-bit integer component type', async ({ + page + }) => { + const { outputImage, baseline } = await page.evaluate(() => { + const itk = window.itk + + const inputImageType = new itk.ImageType() + const inputImage = new itk.Image(inputImageType) + inputImage.size = [256, 256] + inputImage.data = new BigInt64Array(256 * 256) + inputImage.data.fill(7n) + + const outputImage = itk.castImage(inputImage, { + componentType: itk.IntTypes.UInt64 + }) + + const baseline = inputImage + baseline.imageType.componentType = itk.IntTypes.UInt64 + baseline.data = new BigUint64Array(baseline.data.length) + baseline.data.fill(7n) + + return { outputImage, baseline } + }) + compareImageToBaseline(outputImage, baseline) + }) +}) diff --git a/packages/core/typescript/itk-wasm/cypress/e2e/common.ts b/packages/core/typescript/itk-wasm/test/browser/common.js similarity index 78% rename from packages/core/typescript/itk-wasm/cypress/e2e/common.ts rename to packages/core/typescript/itk-wasm/test/browser/common.js index 12fadf2c4..ba1f6f657 100644 --- a/packages/core/typescript/itk-wasm/cypress/e2e/common.ts +++ b/packages/core/typescript/itk-wasm/test/browser/common.js @@ -1,9 +1,7 @@ -export const demoServer = 'http://localhost:5180' +export const pipelineBaseUrl = '/pipelines' +export const pipelineWorkerUrl = '/itk-wasm-pipeline.worker.js' -export const pipelineBaseUrl = new URL('/pipelines', demoServer) -export const pipelineWorkerUrl = new URL('/itk-wasm-pipeline.worker.js', demoServer) - -export async function readIwi(baseUrl: string) { +export async function readIwi(baseUrl) { const imageResponse = await fetch(`${baseUrl}index.json`) const image = await imageResponse.json() const directionResponse = await fetch(`${baseUrl}data/direction.raw`) @@ -17,7 +15,7 @@ export async function readIwi(baseUrl: string) { return image } -export async function readIwm (baseUrl: string) { +export async function readIwm(baseUrl) { const meshResponse = await fetch(`${baseUrl}index.json`) const mesh = await meshResponse.json() const pointsResponse = await fetch(`${baseUrl}data/points.raw`) diff --git a/packages/core/typescript/itk-wasm/test/browser/compare-image-to-baseline.js b/packages/core/typescript/itk-wasm/test/browser/compare-image-to-baseline.js new file mode 100644 index 000000000..25e2737c8 --- /dev/null +++ b/packages/core/typescript/itk-wasm/test/browser/compare-image-to-baseline.js @@ -0,0 +1,12 @@ +import { expect } from '@playwright/test' + +const compareImageToBaseline = (testImage, baselineImage) => { + expect(testImage.imageType).toEqual(baselineImage.imageType) + expect(testImage.origin).toEqual(baselineImage.origin) + expect(testImage.spacing).toEqual(baselineImage.spacing) + expect(testImage.direction).toEqual(baselineImage.direction) + expect(testImage.size).toEqual(baselineImage.size) + expect(testImage.data).toEqual(baselineImage.data) +} + +export default compareImageToBaseline diff --git a/packages/core/typescript/itk-wasm/test/browser/pipeline/pthread-support-available.spec.js b/packages/core/typescript/itk-wasm/test/browser/pipeline/pthread-support-available.spec.js new file mode 100644 index 000000000..e64f39f49 --- /dev/null +++ b/packages/core/typescript/itk-wasm/test/browser/pipeline/pthread-support-available.spec.js @@ -0,0 +1,26 @@ +import { test, expect } from '@playwright/test' +import { pipelineBaseUrl, pipelineWorkerUrl } from '../common.js' + +test.describe('pthreadSupportAvailable', () => { + test('reports whether pthread support is available', async ({ + page, + browserName + }) => { + test.skip(browserName === 'webkit', 'Flaky on webkit') + + await page.goto('/') + + const pthreadSupportAvailable = await page.evaluate( + async ({ pipelineWorkerUrl, pipelineBaseUrl }) => { + const itk = window.itk + itk.setPipelineWorkerUrl(pipelineWorkerUrl) + itk.setPipelinesBaseUrl(pipelineBaseUrl) + + return itk.pthreadSupportAvailable() + }, + { pipelineWorkerUrl, pipelineBaseUrl } + ) + + expect(pthreadSupportAvailable).toBe(true) + }) +}) diff --git a/packages/core/typescript/itk-wasm/test/browser/pipeline/run-pipeline.spec.js b/packages/core/typescript/itk-wasm/test/browser/pipeline/run-pipeline.spec.js new file mode 100644 index 000000000..0b071d97d --- /dev/null +++ b/packages/core/typescript/itk-wasm/test/browser/pipeline/run-pipeline.spec.js @@ -0,0 +1,502 @@ +import { test, expect } from '@playwright/test' +import { pipelineBaseUrl, pipelineWorkerUrl } from '../common.js' + +test.describe('runPipeline', () => { + test.beforeEach(async ({ page }) => { + await page.goto('/') + await page.evaluate( + ({ pipelineWorkerUrl, pipelineBaseUrl }) => { + const itk = window.itk + itk.setPipelineWorkerUrl(pipelineWorkerUrl) + itk.setPipelinesBaseUrl(pipelineBaseUrl) + }, + { pipelineWorkerUrl, pipelineBaseUrl } + ) + }) + + test('captures stdout and stderr', async ({ page }) => { + await page.evaluate(async () => { + const itk = window.itk + + const args = [] + const outputs = null + const inputs = null + const stdoutStderrPath = 'stdout-stderr-test' + await itk.runPipeline(stdoutStderrPath, args, outputs, inputs) + }) + }) + + test('fetches Wasm files from a custom URL', async ({ page }) => { + await page.evaluate( + async ({ pipelineBaseUrl, pipelineWorkerUrl }) => { + const itk = window.itk + + const args = [] + const outputs = null + const inputs = null + const stdoutStderrPath = 'stdout-stderr-test' + await itk.runPipeline(stdoutStderrPath, args, outputs, inputs, { + pipelineBaseUrl, + pipelineWorkerUrl + }) + }, + { pipelineBaseUrl, pipelineWorkerUrl } + ) + }) + + test('fetches Wasm files from a custom URL and query params', async ({ + page + }) => { + await page.evaluate( + async ({ pipelineBaseUrl, pipelineWorkerUrl }) => { + const itk = window.itk + + const args = [] + const outputs = null + const inputs = null + const stdoutStderrPath = 'stdout-stderr-test' + await itk.runPipeline(stdoutStderrPath, args, outputs, inputs, { + pipelineBaseUrl, + pipelineWorkerUrl, + pipelineQueryParams: { key: 'value' } + }) + }, + { pipelineBaseUrl, pipelineWorkerUrl } + ) + }) + + test('fetches Wasm files from a custom pipelineBaseUrl string', async ({ + page + }) => { + await page.evaluate(async () => { + const itk = window.itk + const pipelineBaseUrl = '/pipelines' + + const args = [] + const outputs = null + const inputs = null + const stdoutStderrPath = 'stdout-stderr-test' + await itk.runPipeline(stdoutStderrPath, args, outputs, inputs, { + pipelineBaseUrl + }) + }) + }) + + test('fetches the pipeline web worker from a custom pipelineWorkerUrl string', async ({ + page + }) => { + await page.evaluate(async () => { + const itk = window.itk + const pipelineWorkerUrl = '/itk-wasm-pipeline.worker.js' + + const args = [] + const outputs = null + const inputs = null + const stdoutStderrPath = 'stdout-stderr-test' + await itk.runPipeline(stdoutStderrPath, args, outputs, inputs, { + pipelineWorkerUrl + }) + }) + }) + + test('uses a web worker created explicitly beforehand', async ({ page }) => { + await page.evaluate(async () => { + const itk = window.itk + const pipelineWorkerUrl = '/itk-wasm-pipeline.worker.js' + const webWorker = await itk.createWebWorker(pipelineWorkerUrl) + + const args = [] + const outputs = null + const inputs = null + const stdoutStderrPath = 'stdout-stderr-test' + await itk.runPipeline(stdoutStderrPath, args, outputs, inputs, { + webWorker + }) + }) + }) + + test('re-uses a WebWorker', async ({ page }) => { + const result = await page.evaluate(async () => { + const itk = window.itk + + const args = [] + const outputs = null + const inputs = null + const stdoutStderrPath = 'stdout-stderr-test' + const { webWorker } = await itk.runPipeline( + stdoutStderrPath, + args, + outputs, + inputs + ) + const { returnValue, stdout, stderr } = await itk.runPipeline( + stdoutStderrPath, + args, + outputs, + inputs, + { webWorker } + ) + + const terminatedBefore = webWorker.terminated + webWorker.terminate() + const terminatedAfter = webWorker.terminated + + return { + terminatedBefore, + terminatedAfter, + returnValue, + stdout, + stderr + } + }) + + expect(typeof result.terminatedBefore).toBe('boolean') + expect(result.terminatedBefore).toBe(false) + expect(result.terminatedAfter).toBe(true) + expect(result.returnValue).toBe(0) + expect(result.stdout).toBe(`I’m writing my code, +But I do not realize, +Hours have gone by. +`) + expect(result.stderr).toBe(`The modem humming +Code rapidly compiling. +Click. Perfect success. +`) + }) + + test('runs a pipeline on the main thread with an absolute URL', async ({ + page + }) => { + const result = await page.evaluate(async () => { + const itk = window.itk + + const args = [] + const outputs = null + const inputs = null + const absoluteURL = new URL( + '/pipelines/stdout-stderr-test', + document.location + ) + const { returnValue, stdout, stderr } = await itk.runPipeline( + absoluteURL, + args, + outputs, + inputs, + { webWorker: false } + ) + + return { returnValue, stdout, stderr } + }) + + expect(result.returnValue).toBe(0) + expect(result.stdout).toBe(`I’m writing my code, +But I do not realize, +Hours have gone by. +`) + expect(result.stderr).toBe(`The modem humming +Code rapidly compiling. +Click. Perfect success. +`) + }) + + test('uses input and output text and binary data via memory io', async ({ + page + }) => { + const result = await page.evaluate(async () => { + const itk = window.itk + + const pipelinePath = 'input-output-files-test' + const args = [ + '--memory-io', + '--input-text-stream', + '0', + '--input-binary-stream', + '1', + '0', + '1' + ] + const desiredOutputs = [ + { type: itk.InterfaceTypes.TextStream }, + { type: itk.InterfaceTypes.BinaryStream } + ] + const inputs = [ + { + type: itk.InterfaceTypes.TextStream, + data: { data: 'The answer is 42.' } + }, + { + type: itk.InterfaceTypes.BinaryStream, + data: { data: new Uint8Array([222, 173, 190, 239]) } + } + ] + const { stdout, stderr, outputs, webWorker } = await itk.runPipeline( + pipelinePath, + args, + desiredOutputs, + inputs + ) + webWorker.terminate() + + return { + outputs: outputs.map((output) => ({ + type: output.type, + data: output.data + })), + stdout, + stderr + } + }) + + expect(result.outputs[0].type).toBe('TextStream') + expect(result.outputs[0].data.data).toBe('The answer is 42.') + expect(result.outputs[1].type).toBe('BinaryStream') + expect(result.outputs[1].data.data[0]).toBe(222) + expect(result.outputs[1].data.data[1]).toBe(173) + expect(result.outputs[1].data.data[2]).toBe(190) + expect(result.outputs[1].data.data[3]).toBe(239) + expect(result.stdout).toBe(`Input text: The answer is 42. +`) + expect(result.stderr).toBe(`Input binary: ffffffdeffffffadffffffbeffffffef +`) + }) + + test('runs on the main thread when webWorker option is false', async ({ + page + }) => { + const result = await page.evaluate(async () => { + const itk = window.itk + + const pipelinePath = 'input-output-files-test' + const args = [ + '--memory-io', + '--input-text-stream', + '0', + '--input-binary-stream', + '1', + '0', + '1' + ] + const desiredOutputs = [ + { type: itk.InterfaceTypes.TextStream }, + { type: itk.InterfaceTypes.BinaryStream } + ] + const inputs = [ + { + type: itk.InterfaceTypes.TextStream, + data: { data: 'The answer is 42.' } + }, + { + type: itk.InterfaceTypes.BinaryStream, + data: { data: new Uint8Array([222, 173, 190, 239]) } + } + ] + const { stdout, stderr, outputs } = await itk.runPipeline( + pipelinePath, + args, + desiredOutputs, + inputs, + { webWorker: false } + ) + + return { + outputs: outputs.map((output) => ({ + type: output.type, + data: output.data + })), + stdout, + stderr + } + }) + + expect(result.outputs[0].type).toBe('TextStream') + expect(result.outputs[0].data.data).toBe('The answer is 42.') + expect(result.outputs[1].type).toBe('BinaryStream') + expect(result.outputs[1].data.data[0]).toBe(222) + expect(result.outputs[1].data.data[1]).toBe(173) + expect(result.outputs[1].data.data[2]).toBe(190) + expect(result.outputs[1].data.data[3]).toBe(239) + expect(result.stdout).toBe(`Input text: The answer is 42. +`) + expect(result.stderr).toBe(`Input binary: ffffffdeffffffadffffffbeffffffef +`) + }) + + test("writes and reads itk.Image's", async ({ page }) => { + await page.evaluate(async () => { + const itk = window.itk + + const verifyImage = (image) => { + if (image.imageType.dimension !== 2) + throw new Error('dimension mismatch') + if (image.imageType.componentType !== itk.IntTypes.UInt8) + throw new Error('componentType mismatch') + if (image.imageType.pixelType !== itk.PixelTypes.Scalar) + throw new Error('pixelType mismatch') + if (image.imageType.components !== 1) + throw new Error('components mismatch') + if (JSON.stringify(image.origin) !== JSON.stringify([0.0, 0.0])) + throw new Error('origin mismatch') + if (JSON.stringify(image.spacing) !== JSON.stringify([1.0, 1.0])) + throw new Error('spacing mismatch') + if (JSON.stringify(image.size) !== JSON.stringify([256, 256])) + throw new Error('size mismatch') + if (image.data.byteLength !== 65536) + throw new Error('data.byteLength mismatch') + } + + const readIwi = async (baseUrl) => { + const imageResponse = await fetch(`${baseUrl}index.json`) + const image = await imageResponse.json() + const directionResponse = await fetch(`${baseUrl}data/direction.raw`) + const directionBuffer = await directionResponse.arrayBuffer() + const directionData = new Float64Array(directionBuffer) + image.direction = directionData + const dataResponse = await fetch(`${baseUrl}data/data.raw`) + const dataBuffer = await dataResponse.arrayBuffer() + const pixelData = new Uint8Array(dataBuffer) + image.data = pixelData + return image + } + + const cthead1BaseUrl = '/data/cthead1.iwi/' + const image = await readIwi(cthead1BaseUrl) + + const pipelinePath = 'median-filter-test' + const args = ['0', '0', '--radius', '4', '--memory-io'] + const desiredOutputs = [{ type: itk.InterfaceTypes.Image }] + const inputs = [{ type: itk.InterfaceTypes.Image, data: image }] + const { webWorker, outputs } = await itk.runPipeline( + pipelinePath, + args, + desiredOutputs, + inputs + ) + webWorker.terminate() + verifyImage(outputs[0].data) + }) + }) + + test('runs twice without a detached buffer', async ({ page }) => { + await page.evaluate(async () => { + const itk = window.itk + + const verifyImage = (image) => { + if (image.imageType.dimension !== 2) + throw new Error('dimension mismatch') + if (image.imageType.componentType !== itk.IntTypes.UInt8) + throw new Error('componentType mismatch') + if (image.imageType.pixelType !== itk.PixelTypes.Scalar) + throw new Error('pixelType mismatch') + if (image.imageType.components !== 1) + throw new Error('components mismatch') + if (JSON.stringify(image.origin) !== JSON.stringify([0.0, 0.0])) + throw new Error('origin mismatch') + if (JSON.stringify(image.spacing) !== JSON.stringify([1.0, 1.0])) + throw new Error('spacing mismatch') + if (JSON.stringify(image.size) !== JSON.stringify([256, 256])) + throw new Error('size mismatch') + if (image.data.byteLength !== 65536) + throw new Error('data.byteLength mismatch') + } + + const readIwi = async (baseUrl) => { + const imageResponse = await fetch(`${baseUrl}index.json`) + const image = await imageResponse.json() + const directionResponse = await fetch(`${baseUrl}data/direction.raw`) + const directionBuffer = await directionResponse.arrayBuffer() + const directionData = new Float64Array(directionBuffer) + image.direction = directionData + const dataResponse = await fetch(`${baseUrl}data/data.raw`) + const dataBuffer = await dataResponse.arrayBuffer() + const pixelData = new Uint8Array(dataBuffer) + image.data = pixelData + return image + } + + const cthead1BaseUrl = '/data/cthead1.iwi/' + const image = await readIwi(cthead1BaseUrl) + + const pipelinePath = 'median-filter-test' + const args = ['0', '0', '--radius', '4', '--memory-io'] + const desiredOutputs = [{ type: itk.InterfaceTypes.Image }] + const inputs = [{ type: itk.InterfaceTypes.Image, data: image }] + // const options = { noCopy: true } // failure expected + const options = { noCopy: false } + const { webWorker } = await itk.runPipeline( + pipelinePath, + args, + desiredOutputs, + inputs, + options + ) + options.webWorker = webWorker + const { outputs } = await itk.runPipeline( + pipelinePath, + args, + desiredOutputs, + inputs, + options + ) + webWorker.terminate() + + verifyImage(outputs[0].data) + }) + }) + + test('runPipeline writes and reads an itk.Mesh via memory io', async ({ + page + }) => { + await page.evaluate(async () => { + const itk = window.itk + + const verifyMesh = (mesh) => { + if (mesh.meshType.dimension !== 3) throw new Error('dimension mismatch') + if (mesh.meshType.pointComponentType !== itk.FloatTypes.Float32) + throw new Error('pointComponentType mismatch') + if (mesh.meshType.cellComponentType !== itk.IntTypes.UInt32) + throw new Error('cellComponentType mismatch') + if (mesh.meshType.pointPixelType !== itk.PixelTypes.Scalar) + throw new Error('pointPixelType mismatch') + if (mesh.meshType.cellPixelType !== itk.PixelTypes.Scalar) + throw new Error('cellPixelType mismatch') + if (mesh.numberOfPoints !== 2903) + throw new Error('numberOfPoints mismatch') + if (mesh.numberOfCells !== 3263) + throw new Error('numberOfCells mismatch') + } + + const readIwm = async (baseUrl) => { + const meshResponse = await fetch(`${baseUrl}index.json`) + const mesh = await meshResponse.json() + const pointsResponse = await fetch(`${baseUrl}data/points.raw`) + const pointsBuffer = await pointsResponse.arrayBuffer() + const points = new Float32Array(pointsBuffer) + mesh.points = points + const cellsResponse = await fetch(`${baseUrl}data/cells.raw`) + const cellsBuffer = await cellsResponse.arrayBuffer() + const cells = new Float32Array(cellsBuffer) + mesh.cells = cells + // todo + mesh.pointData = null + mesh.cellData = null + return mesh + } + + const cowBaseUrl = '/data/cow.iwm/' + const mesh = await readIwm(cowBaseUrl) + + const pipelinePath = 'mesh-read-write-test' + const args = ['0', '0', '--memory-io'] + const desiredOutputs = [{ type: itk.InterfaceTypes.Mesh }] + const inputs = [{ type: itk.InterfaceTypes.Mesh, data: mesh }] + const { webWorker, outputs } = await itk.runPipeline( + pipelinePath, + args, + desiredOutputs, + inputs + ) + webWorker.terminate() + verifyMesh(outputs[0].data) + }) + }) +}) diff --git a/packages/core/typescript/itk-wasm/test/browser/web-worker-pool.spec.js b/packages/core/typescript/itk-wasm/test/browser/web-worker-pool.spec.js new file mode 100644 index 000000000..be0d7cdf2 --- /dev/null +++ b/packages/core/typescript/itk-wasm/test/browser/web-worker-pool.spec.js @@ -0,0 +1,84 @@ +import { test, expect } from '@playwright/test' +import { readIwi, pipelineBaseUrl, pipelineWorkerUrl } from './common.js' +import compareImageToBaseline from './compare-image-to-baseline.js' + +test.describe('WebWorkerPool', () => { + test('runs and reports progress', async ({ page }) => { + await page.goto('/') + + const result = await page.evaluate( + async ({ pipelineWorkerUrl, pipelineBaseUrl, readIwi }) => { + const itk = window.itk + itk.setPipelineWorkerUrl(pipelineWorkerUrl) + itk.setPipelinesBaseUrl(pipelineBaseUrl) + + const progressUpdates = [] + let reportedTotalSplits = null + function progressLogger(split, totalSplits) { + progressUpdates.push(split) + reportedTotalSplits = totalSplits + } + + const poolSize = 2 + const maxTotalSplits = 4 + const workerPool = new itk.WorkerPool(poolSize, itk.runPipeline) + + // Use the readIwi function passed from Node.js context + const readIwiInBrowser = new Function('return ' + readIwi)() + + const cthead1BaseUrl = '/data/cthead1.iwi/' + const image = await readIwiInBrowser(cthead1BaseUrl) + + const taskArgsArray = [] + for (let index = 0; index < maxTotalSplits; index++) { + const pipelinePath = 'median-filter-test' + const args = [ + '--memory-io', + '0', + '0', + '--radius', + '4', + '-m', + '' + maxTotalSplits, + '-s', + '' + index + ] + const desiredOutputs = [{ type: itk.InterfaceTypes.Image }] + const data = itk.imageSharedBufferOrCopy(image) + const inputs = [{ type: itk.InterfaceTypes.Image, data }] + taskArgsArray.push([pipelinePath, args, desiredOutputs, inputs, {}]) + } + + const results = await workerPool.runTasks(taskArgsArray, progressLogger) + .promise + workerPool.terminateWorkers() + + const imageSplits = results.map(({ outputs }) => outputs[0].data) + const stackedImage = itk.stackImages(imageSplits) + + const baselineBaseUrl = '/data/web-worker-pool-baseline.iwi/' + const baselineImage = await readIwiInBrowser(baselineBaseUrl) + + return { + reportedTotalSplits, + maxTotalSplits, + stackedImage: JSON.parse(JSON.stringify(stackedImage)), + baselineImage: JSON.parse(JSON.stringify(baselineImage)) + } + }, + { + pipelineWorkerUrl, + pipelineBaseUrl, + readIwi: readIwi.toString() // Pass function as string to be reconstructed in browser + } + ) + + expect(result.reportedTotalSplits).toBe(result.maxTotalSplits) + + // Reconstruct the images for comparison + const stackedImage = result.stackedImage + const baselineImage = result.baselineImage + + compareImageToBaseline(stackedImage, baselineImage) + }) +}) diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 33cb2d25b..754864ccb 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -28,6 +28,9 @@ importers: '@commitlint/config-conventional': specifier: ^19.2.2 version: 19.2.2 + '@playwright/test': + specifier: ^1.53.0 + version: 1.53.1 examples/debugging: dependencies: @@ -466,15 +469,15 @@ importers: specifier: ^1.6.1 version: 1.6.1 devDependencies: + '@playwright/test': + specifier: ^1.53.0 + version: 1.53.1 '@types/node': specifier: ^22.13.13 version: 22.14.0 ava: specifier: ^6.1.3 version: 6.1.3 - cypress: - specifier: ^14.5.0 - version: 14.5.0 esbuild: specifier: ^0.25.1 version: 0.25.2 @@ -490,9 +493,6 @@ importers: standard: specifier: ^17.1.0 version: 17.1.0(@typescript-eslint/parser@5.62.0(eslint@8.57.0)(typescript@5.8.3)) - start-server-and-test: - specifier: ^2.0.12 - version: 2.0.12 ts-standard: specifier: ^12.0.2 version: 12.0.2(typescript@5.8.3) @@ -2050,6 +2050,11 @@ packages: '@perma/map@1.0.3': resolution: {integrity: sha512-Bf5njk0fnJGTFE2ETntq0N1oJ6YdCPIpTDn3R3KYZJQdeYSOCNL7mBrFlGnbqav8YQhJA/p81pvHINX9vAtHkQ==} + '@playwright/test@1.53.1': + resolution: {integrity: sha512-Z4c23LHV0muZ8hfv4jw6HngPJkbbtZxTkxPNIg7cJcTc9C28N/p2q7g3JZS2SiKBBHJ3uM1dgDye66bB7LEk5w==} + engines: {node: '>=18'} + hasBin: true + '@protobufjs/aspromise@1.1.2': resolution: {integrity: sha512-j+gKExEuLmKwvz3OgROXtrJ2UG2x8Ch2YZUxahh+s1F2HZ+wAceUNLkvy6zKCPVRkU++ZWQrdxsUeQXmcg4uoQ==} @@ -3752,6 +3757,11 @@ packages: fs.realpath@1.0.0: resolution: {integrity: sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==} + fsevents@2.3.2: + resolution: {integrity: sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==} + engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} + os: [darwin] + fsevents@2.3.3: resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==} engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} @@ -5022,6 +5032,16 @@ packages: resolution: {integrity: sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==} engines: {node: '>=8'} + playwright-core@1.53.1: + resolution: {integrity: sha512-Z46Oq7tLAyT0lGoFx4DOuB1IA9D1TPj0QkYxpPVUnGDqHHvDpCftu1J2hM2PiWsNMoZh8+LQaarAWcDfPBc6zg==} + engines: {node: '>=18'} + hasBin: true + + playwright@1.53.1: + resolution: {integrity: sha512-LJ13YLr/ocweuwxyGf1XNFWIU4M2zUSo149Qbp+A4cpwDjsxRPj7k6H25LBrEHiEwxvRbD8HdwvQmRMSvquhYw==} + engines: {node: '>=18'} + hasBin: true + plur@5.1.0: resolution: {integrity: sha512-VP/72JeXqak2KiOzjgKtQen5y3IZHn+9GOuLDafPv0eXa47xq0At93XahYBs26MsifCQ4enGKwbjBTKgb9QJXg==} engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} @@ -7773,6 +7793,10 @@ snapshots: '@multiformats/murmur3': 2.1.8 murmurhash3js-revisited: 3.0.0 + '@playwright/test@1.53.1': + dependencies: + playwright: 1.53.1 + '@protobufjs/aspromise@1.1.2': {} '@protobufjs/base64@1.1.2': {} @@ -9767,6 +9791,9 @@ snapshots: fs.realpath@1.0.0: {} + fsevents@2.3.2: + optional: true + fsevents@2.3.3: optional: true @@ -11033,6 +11060,14 @@ snapshots: dependencies: find-up: 4.1.0 + playwright-core@1.53.1: {} + + playwright@1.53.1: + dependencies: + playwright-core: 1.53.1 + optionalDependencies: + fsevents: 2.3.2 + plur@5.1.0: dependencies: irregular-plurals: 3.5.0 From 53147cff88eaa95c5e30196d634f7bdb74ea2833 Mon Sep 17 00:00:00 2001 From: Matthew McCormick Date: Wed, 18 Jun 2025 17:28:13 -0400 Subject: [PATCH 2/3] chore(deps): bump @types/node to 24.0.3 --- package.json | 2 +- .../test/pipelines/typescript/package.json | 2 +- pnpm-lock.yaml | 175 +++++++++--------- 3 files changed, 86 insertions(+), 93 deletions(-) diff --git a/package.json b/package.json index e19e092d6..00c41597a 100644 --- a/package.json +++ b/package.json @@ -41,7 +41,7 @@ "pnpm": { "overrides": { "@shoelace-style/shoelace": "^2.12.0", - "@types/node": "^22.13.13", + "@types/node": "^24.0.3", "esbuild": "^0.25.1", "start-server-and-test": "^2.0.12", "ava": "^6.1.3", diff --git a/packages/core/typescript/itk-wasm/test/pipelines/typescript/package.json b/packages/core/typescript/itk-wasm/test/pipelines/typescript/package.json index 3ab5f2e81..fb8a2f885 100644 --- a/packages/core/typescript/itk-wasm/test/pipelines/typescript/package.json +++ b/packages/core/typescript/itk-wasm/test/pipelines/typescript/package.json @@ -48,4 +48,4 @@ "vite-plugin-cross-origin-isolation": "^0.1.6", "vite-plugin-static-copy": "^0.17.0" } -} +} \ No newline at end of file diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 754864ccb..5bed166a2 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -6,7 +6,7 @@ settings: overrides: '@shoelace-style/shoelace': ^2.12.0 - '@types/node': ^22.13.13 + '@types/node': ^24.0.3 esbuild: ^0.25.1 start-server-and-test: ^2.0.12 ava: ^6.1.3 @@ -24,7 +24,7 @@ importers: version: 2.27.1 '@commitlint/cli': specifier: ^19.3.0 - version: 19.3.0(@types/node@22.15.21)(typescript@5.8.3) + version: 19.3.0(@types/node@24.0.3)(typescript@5.8.3) '@commitlint/config-conventional': specifier: ^19.2.2 version: 19.2.2 @@ -94,8 +94,8 @@ importers: specifier: workspace:^ version: link:../../../packages/mesh-io/typescript '@types/node': - specifier: ^22.13.13 - version: 22.14.0 + specifier: ^24.0.3 + version: 24.0.3 esbuild: specifier: ^0.25.1 version: 0.25.2 @@ -107,10 +107,10 @@ importers: version: 5.8.3 vite: specifier: ^6.2.3 - version: 6.2.7(@types/node@22.14.0)(jiti@1.21.0)(terser@5.39.2) + version: 6.2.7(@types/node@24.0.3)(jiti@1.21.0)(terser@5.39.2) vite-plugin-static-copy: specifier: ^0.17.0 - version: 0.17.1(vite@6.2.7(@types/node@22.14.0)(jiti@1.21.0)(terser@5.39.2)) + version: 0.17.1(vite@6.2.7(@types/node@24.0.3)(jiti@1.21.0)(terser@5.39.2)) examples/mean-squares-versor-registration: devDependencies: @@ -140,8 +140,8 @@ importers: specifier: workspace:^ version: link:../../../packages/mesh-io/typescript '@types/node': - specifier: ^22.13.13 - version: 22.14.0 + specifier: ^24.0.3 + version: 24.0.3 esbuild: specifier: ^0.25.1 version: 0.25.2 @@ -153,10 +153,10 @@ importers: version: 5.8.3 vite: specifier: ^6.2.3 - version: 6.2.7(@types/node@22.14.0)(jiti@1.21.0)(terser@5.39.2) + version: 6.2.7(@types/node@24.0.3)(jiti@1.21.0)(terser@5.39.2) vite-plugin-static-copy: specifier: ^0.17.0 - version: 0.17.1(vite@6.2.7(@types/node@22.14.0)(jiti@1.21.0)(terser@5.39.2)) + version: 0.17.1(vite@6.2.7(@types/node@24.0.3)(jiti@1.21.0)(terser@5.39.2)) examples/node-js: dependencies: @@ -201,8 +201,8 @@ importers: specifier: workspace:^ version: link:../../image-io/typescript '@types/node': - specifier: ^22.13.13 - version: 22.14.0 + specifier: ^24.0.3 + version: 24.0.3 ava: specifier: ^6.1.3 version: 6.1.3 @@ -223,10 +223,10 @@ importers: version: 5.8.3 vite: specifier: ^6.2.3 - version: 6.2.7(@types/node@22.14.0)(jiti@1.21.0)(terser@5.39.2) + version: 6.2.7(@types/node@24.0.3)(jiti@1.21.0)(terser@5.39.2) vite-plugin-static-copy: specifier: ^1.0.0 - version: 1.0.6(vite@6.2.7(@types/node@22.14.0)(jiti@1.21.0)(terser@5.39.2)) + version: 1.0.6(vite@6.2.7(@types/node@24.0.3)(jiti@1.21.0)(terser@5.39.2)) packages/compare-meshes: devDependencies: @@ -256,8 +256,8 @@ importers: specifier: workspace:* version: link:../../mesh-io/typescript '@types/node': - specifier: ^22.13.13 - version: 22.14.0 + specifier: ^24.0.3 + version: 24.0.3 esbuild: specifier: ^0.25.1 version: 0.25.2 @@ -269,10 +269,10 @@ importers: version: 5.8.3 vite: specifier: ^6.2.3 - version: 6.2.7(@types/node@22.14.0)(jiti@1.21.0)(terser@5.39.2) + version: 6.2.7(@types/node@24.0.3)(jiti@1.21.0)(terser@5.39.2) vite-plugin-static-copy: specifier: ^0.17.0 - version: 0.17.1(vite@6.2.7(@types/node@22.14.0)(jiti@1.21.0)(terser@5.39.2)) + version: 0.17.1(vite@6.2.7(@types/node@24.0.3)(jiti@1.21.0)(terser@5.39.2)) packages/compress-stringify: devDependencies: @@ -320,8 +320,8 @@ importers: specifier: ^1.1.0 version: 1.1.0 '@types/node': - specifier: ^22.13.13 - version: 22.14.0 + specifier: ^24.0.3 + version: 24.0.3 ava: specifier: ^6.1.3 version: 6.1.3 @@ -342,10 +342,10 @@ importers: version: 5.8.3 vite: specifier: ^6.2.3 - version: 6.2.7(@types/node@22.14.0)(jiti@1.21.0)(terser@5.39.2) + version: 6.2.7(@types/node@24.0.3)(jiti@1.21.0)(terser@5.39.2) vite-plugin-static-copy: specifier: ^0.17.0 - version: 0.17.1(vite@6.2.7(@types/node@22.14.0)(jiti@1.21.0)(terser@5.39.2)) + version: 0.17.1(vite@6.2.7(@types/node@24.0.3)(jiti@1.21.0)(terser@5.39.2)) packages/core/typescript/create-itk-wasm: dependencies: @@ -381,8 +381,8 @@ importers: specifier: ^9.0.7 version: 9.0.7 '@types/node': - specifier: ^22.13.13 - version: 22.14.0 + specifier: ^24.0.3 + version: 24.0.3 '@types/validate-npm-package-name': specifier: ^4.0.2 version: 4.0.2 @@ -473,8 +473,8 @@ importers: specifier: ^1.53.0 version: 1.53.1 '@types/node': - specifier: ^22.13.13 - version: 22.14.0 + specifier: ^24.0.3 + version: 24.0.3 ava: specifier: ^6.1.3 version: 6.1.3 @@ -519,8 +519,8 @@ importers: specifier: workspace:* version: link:../../../../../../transform-io/typescript '@types/node': - specifier: ^22.13.13 - version: 22.14.0 + specifier: ^24.0.3 + version: 24.0.3 esbuild: specifier: ^0.25.1 version: 0.25.2 @@ -532,13 +532,13 @@ importers: version: 5.8.3 vite: specifier: ^6.2.3 - version: 6.2.7(@types/node@22.14.0)(jiti@1.21.0)(terser@5.39.2) + version: 6.2.7(@types/node@24.0.3)(jiti@1.21.0)(terser@5.39.2) vite-plugin-cross-origin-isolation: specifier: ^0.1.6 version: 0.1.6 vite-plugin-static-copy: specifier: ^0.17.0 - version: 0.17.1(vite@6.2.7(@types/node@22.14.0)(jiti@1.21.0)(terser@5.39.2)) + version: 0.17.1(vite@6.2.7(@types/node@24.0.3)(jiti@1.21.0)(terser@5.39.2)) packages/dicom: devDependencies: @@ -574,8 +574,8 @@ importers: specifier: workspace:* version: link:../../image-io/typescript '@types/node': - specifier: ^22.13.13 - version: 22.14.0 + specifier: ^24.0.3 + version: 24.0.3 ava: specifier: ^6.1.3 version: 6.1.3 @@ -602,10 +602,10 @@ importers: version: 5.8.3 vite: specifier: ^6.2.3 - version: 6.2.7(@types/node@22.14.0)(jiti@1.21.0)(terser@5.39.2) + version: 6.2.7(@types/node@24.0.3)(jiti@1.21.0)(terser@5.39.2) vite-plugin-static-copy: specifier: ^0.17.0 - version: 0.17.1(vite@6.2.7(@types/node@22.14.0)(jiti@1.21.0)(terser@5.39.2)) + version: 0.17.1(vite@6.2.7(@types/node@24.0.3)(jiti@1.21.0)(terser@5.39.2)) packages/downsample: devDependencies: @@ -644,8 +644,8 @@ importers: specifier: workspace:^ version: link:../../mesh-io/typescript '@types/node': - specifier: ^22.13.13 - version: 22.14.0 + specifier: ^24.0.3 + version: 24.0.3 ava: specifier: ^6.1.3 version: 6.1.3 @@ -666,10 +666,10 @@ importers: version: 5.8.3 vite: specifier: ^6.2.3 - version: 6.2.7(@types/node@22.14.0)(jiti@1.21.0)(terser@5.39.2) + version: 6.2.7(@types/node@24.0.3)(jiti@1.21.0)(terser@5.39.2) vite-plugin-static-copy: specifier: ^0.17.0 - version: 0.17.1(vite@6.2.7(@types/node@22.14.0)(jiti@1.21.0)(terser@5.39.2)) + version: 0.17.1(vite@6.2.7(@types/node@24.0.3)(jiti@1.21.0)(terser@5.39.2)) packages/image-io: devDependencies: @@ -702,8 +702,8 @@ importers: specifier: ^2.1.4 version: 2.1.4 '@types/node': - specifier: ^22.13.13 - version: 22.14.0 + specifier: ^24.0.3 + version: 24.0.3 ava: specifier: ^6.1.3 version: 6.1.3 @@ -727,10 +727,10 @@ importers: version: 5.8.3 vite: specifier: ^6.2.3 - version: 6.2.7(@types/node@22.14.0)(jiti@1.21.0)(terser@5.39.2) + version: 6.2.7(@types/node@24.0.3)(jiti@1.21.0)(terser@5.39.2) vite-plugin-static-copy: specifier: ^1.0.0 - version: 1.0.6(vite@6.2.7(@types/node@22.14.0)(jiti@1.21.0)(terser@5.39.2)) + version: 1.0.6(vite@6.2.7(@types/node@24.0.3)(jiti@1.21.0)(terser@5.39.2)) packages/mesh-filters: devDependencies: @@ -766,8 +766,8 @@ importers: specifier: workspace:* version: link:../../mesh-io/typescript '@types/node': - specifier: ^22.13.13 - version: 22.14.0 + specifier: ^24.0.3 + version: 24.0.3 ava: specifier: ^6.1.3 version: 6.1.3 @@ -782,10 +782,10 @@ importers: version: 5.8.3 vite: specifier: ^6.2.3 - version: 6.2.7(@types/node@22.14.0)(jiti@1.21.0)(terser@5.39.2) + version: 6.2.7(@types/node@24.0.3)(jiti@1.21.0)(terser@5.39.2) vite-plugin-static-copy: specifier: ^0.17.0 - version: 0.17.1(vite@6.2.7(@types/node@22.14.0)(jiti@1.21.0)(terser@5.39.2)) + version: 0.17.1(vite@6.2.7(@types/node@24.0.3)(jiti@1.21.0)(terser@5.39.2)) packages/mesh-io: devDependencies: @@ -818,8 +818,8 @@ importers: specifier: ^2.1.4 version: 2.1.4 '@types/node': - specifier: ^22.13.13 - version: 22.14.0 + specifier: ^24.0.3 + version: 24.0.3 ava: specifier: ^6.1.3 version: 6.1.3 @@ -840,10 +840,10 @@ importers: version: 5.8.3 vite: specifier: ^6.2.3 - version: 6.2.7(@types/node@22.14.0)(jiti@1.21.0)(terser@5.39.2) + version: 6.2.7(@types/node@24.0.3)(jiti@1.21.0)(terser@5.39.2) vite-plugin-static-copy: specifier: ^0.17.0 - version: 0.17.1(vite@6.2.7(@types/node@22.14.0)(jiti@1.21.0)(terser@5.39.2)) + version: 0.17.1(vite@6.2.7(@types/node@24.0.3)(jiti@1.21.0)(terser@5.39.2)) packages/transform-io: devDependencies: @@ -873,8 +873,8 @@ importers: specifier: ^2.1.4 version: 2.1.4 '@types/node': - specifier: ^22.13.13 - version: 22.14.0 + specifier: ^24.0.3 + version: 24.0.3 ava: specifier: ^6.1.3 version: 6.1.3 @@ -895,10 +895,10 @@ importers: version: 5.8.3 vite: specifier: ^6.2.3 - version: 6.2.7(@types/node@22.14.0)(jiti@1.21.0)(terser@5.39.2) + version: 6.2.7(@types/node@24.0.3)(jiti@1.21.0)(terser@5.39.2) vite-plugin-static-copy: specifier: ^0.17.0 - version: 0.17.1(vite@6.2.7(@types/node@22.14.0)(jiti@1.21.0)(terser@5.39.2)) + version: 0.17.1(vite@6.2.7(@types/node@24.0.3)(jiti@1.21.0)(terser@5.39.2)) packages: @@ -2262,11 +2262,8 @@ packages: '@types/node-fetch@2.6.11': resolution: {integrity: sha512-24xFj9R5+rfQJLRyM56qh+wnVSYhyXC2tkoBndtY0U+vubqNsYXGjufB2nn8Q6gt0LrARwL6UBtMCSVCwl4B1g==} - '@types/node@22.14.0': - resolution: {integrity: sha512-Kmpl+z84ILoG+3T/zQFyAJsU6EPTmOCj8/2+83fSN6djd6I4o7uOuGIH6vq3PrjY5BGitSbFuMN18j3iknubbA==} - - '@types/node@22.15.21': - resolution: {integrity: sha512-EV/37Td6c+MgKAbkcLG6vqZ2zEYHD7bvSrzqqs2RIhbA6w3x+Dqz8MZM3sP6kGTeLrdoOgKZe+Xja7tUB2DNkQ==} + '@types/node@24.0.3': + resolution: {integrity: sha512-R4I/kzCYAdRLzfiCabn9hxWfbuHS573x+r0dJMkkzThEa7pbrcDWK+9zu3e7aBOouf+rQAciqPFMnxwr0aWgKg==} '@types/normalize-package-data@2.4.4': resolution: {integrity: sha512-37i+OaWTh9qeK4LSHPsyRC7NahnGotNuZvjLSgcPzblpHB3rrCJxAOgI5gCdKm7coonsaX1Of0ILiTcnZjbfxA==} @@ -3032,7 +3029,7 @@ packages: resolution: {integrity: sha512-+8cK7jRAReYkMwMiG+bxhcNKiHJDM6bR9FD/nGBXOWdMLuYawjF5cGrtLilJ+LGd3ZjCXnJjR5DkfWPoIVlqJA==} engines: {node: '>=v16'} peerDependencies: - '@types/node': ^22.13.13 + '@types/node': ^24.0.3 cosmiconfig: '>=8.2' typescript: ^5.8.3 @@ -5832,8 +5829,8 @@ packages: unbzip2-stream@1.4.3: resolution: {integrity: sha512-mlExGW4w71ebDJviH16lQLtZS32VKqsSfk80GCfUlwT/4/hNRFsoscrF/c++9xinkMzECL1uL9DDwXqFWkruPg==} - undici-types@6.21.0: - resolution: {integrity: sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==} + undici-types@7.8.0: + resolution: {integrity: sha512-9UJ2xGDvQ43tYyVMpuHlsgApydB8ZKfVYTsLDhXkFL/6gfkp+U8xTGdh8pMJv1SpZna0zxG1DwsKZsreLbXBxw==} undici@5.28.4: resolution: {integrity: sha512-72RFADWFqKmUb2hmmvNODKL3p9hcB6Gt2DOQMis1SEBaV6a4MH8soBvzg+95CYhCKPFedut2JY9bMfrDl9D23g==} @@ -5945,7 +5942,7 @@ packages: engines: {node: ^18.0.0 || ^20.0.0 || >=22.0.0} hasBin: true peerDependencies: - '@types/node': ^22.13.13 + '@types/node': ^24.0.3 jiti: '>=1.21.0' less: '*' lightningcss: ^1.21.0 @@ -7203,11 +7200,11 @@ snapshots: dependencies: commander: 12.0.0 - '@commitlint/cli@19.3.0(@types/node@22.15.21)(typescript@5.8.3)': + '@commitlint/cli@19.3.0(@types/node@24.0.3)(typescript@5.8.3)': dependencies: '@commitlint/format': 19.3.0 '@commitlint/lint': 19.2.2 - '@commitlint/load': 19.2.0(@types/node@22.15.21)(typescript@5.8.3) + '@commitlint/load': 19.2.0(@types/node@24.0.3)(typescript@5.8.3) '@commitlint/read': 19.2.1 '@commitlint/types': 19.0.3 execa: 8.0.1 @@ -7254,7 +7251,7 @@ snapshots: '@commitlint/rules': 19.0.3 '@commitlint/types': 19.0.3 - '@commitlint/load@19.2.0(@types/node@22.15.21)(typescript@5.8.3)': + '@commitlint/load@19.2.0(@types/node@24.0.3)(typescript@5.8.3)': dependencies: '@commitlint/config-validator': 19.0.3 '@commitlint/execute-rule': 19.0.0 @@ -7262,7 +7259,7 @@ snapshots: '@commitlint/types': 19.0.3 chalk: 5.3.0 cosmiconfig: 9.0.0(typescript@5.8.3) - cosmiconfig-typescript-loader: 5.0.0(@types/node@22.15.21)(cosmiconfig@9.0.0(typescript@5.8.3))(typescript@5.8.3) + cosmiconfig-typescript-loader: 5.0.0(@types/node@24.0.3)(cosmiconfig@9.0.0(typescript@5.8.3))(typescript@5.8.3) lodash.isplainobject: 4.0.6 lodash.merge: 4.6.2 lodash.uniq: 4.5.0 @@ -7707,7 +7704,7 @@ snapshots: '@manypkg/find-root@1.1.0': dependencies: '@babel/runtime': 7.24.5 - '@types/node': 22.14.0 + '@types/node': 24.0.3 find-up: 4.1.0 fs-extra: 8.1.0 @@ -7935,7 +7932,7 @@ snapshots: '@types/conventional-commits-parser@5.0.0': dependencies: - '@types/node': 22.14.0 + '@types/node': 24.0.3 '@types/emscripten@1.39.11': {} @@ -7968,16 +7965,12 @@ snapshots: '@types/node-fetch@2.6.11': dependencies: - '@types/node': 22.14.0 + '@types/node': 24.0.3 form-data: 4.0.2 - '@types/node@22.14.0': - dependencies: - undici-types: 6.21.0 - - '@types/node@22.15.21': + '@types/node@24.0.3': dependencies: - undici-types: 6.21.0 + undici-types: 7.8.0 '@types/normalize-package-data@2.4.4': {} @@ -7996,13 +7989,13 @@ snapshots: '@types/through@0.0.33': dependencies: - '@types/node': 22.14.0 + '@types/node': 24.0.3 '@types/trusted-types@2.0.7': {} '@types/tunnel@0.0.3': dependencies: - '@types/node': 22.14.0 + '@types/node': 24.0.3 '@types/validate-npm-package-name@4.0.2': {} @@ -8010,7 +8003,7 @@ snapshots: '@types/yauzl@2.10.3': dependencies: - '@types/node': 22.15.21 + '@types/node': 24.0.3 optional: true '@typescript-eslint/eslint-plugin@5.62.0(@typescript-eslint/parser@5.62.0(eslint@8.57.0)(typescript@5.8.3))(eslint@8.57.0)(typescript@5.8.3)': @@ -8855,9 +8848,9 @@ snapshots: corser@2.0.1: {} - cosmiconfig-typescript-loader@5.0.0(@types/node@22.15.21)(cosmiconfig@9.0.0(typescript@5.8.3))(typescript@5.8.3): + cosmiconfig-typescript-loader@5.0.0(@types/node@24.0.3)(cosmiconfig@9.0.0(typescript@5.8.3))(typescript@5.8.3): dependencies: - '@types/node': 22.15.21 + '@types/node': 24.0.3 cosmiconfig: 9.0.0(typescript@5.8.3) jiti: 1.21.0 typescript: 5.8.3 @@ -10425,7 +10418,7 @@ snapshots: jest-worker@27.5.1: dependencies: - '@types/node': 22.15.21 + '@types/node': 24.0.3 merge-stream: 2.0.0 supports-color: 8.1.1 @@ -11137,7 +11130,7 @@ snapshots: '@protobufjs/path': 1.1.2 '@protobufjs/pool': 1.1.0 '@protobufjs/utf8': 1.1.0 - '@types/node': 22.14.0 + '@types/node': 24.0.3 long: 5.2.3 protons-runtime@5.4.0: @@ -11984,7 +11977,7 @@ snapshots: buffer: 5.7.1 through: 2.3.8 - undici-types@6.21.0: {} + undici-types@7.8.0: {} undici@5.28.4: dependencies: @@ -12054,29 +12047,29 @@ snapshots: vite-plugin-cross-origin-isolation@0.1.6: {} - vite-plugin-static-copy@0.17.1(vite@6.2.7(@types/node@22.14.0)(jiti@1.21.0)(terser@5.39.2)): + vite-plugin-static-copy@0.17.1(vite@6.2.7(@types/node@24.0.3)(jiti@1.21.0)(terser@5.39.2)): dependencies: chokidar: 3.6.0 fast-glob: 3.3.2 fs-extra: 11.2.0 picocolors: 1.1.1 - vite: 6.2.7(@types/node@22.14.0)(jiti@1.21.0)(terser@5.39.2) + vite: 6.2.7(@types/node@24.0.3)(jiti@1.21.0)(terser@5.39.2) - vite-plugin-static-copy@1.0.6(vite@6.2.7(@types/node@22.14.0)(jiti@1.21.0)(terser@5.39.2)): + vite-plugin-static-copy@1.0.6(vite@6.2.7(@types/node@24.0.3)(jiti@1.21.0)(terser@5.39.2)): dependencies: chokidar: 3.6.0 fast-glob: 3.3.2 fs-extra: 11.2.0 picocolors: 1.1.1 - vite: 6.2.7(@types/node@22.14.0)(jiti@1.21.0)(terser@5.39.2) + vite: 6.2.7(@types/node@24.0.3)(jiti@1.21.0)(terser@5.39.2) - vite@6.2.7(@types/node@22.14.0)(jiti@1.21.0)(terser@5.39.2): + vite@6.2.7(@types/node@24.0.3)(jiti@1.21.0)(terser@5.39.2): dependencies: esbuild: 0.25.2 postcss: 8.5.3 rollup: 4.41.0 optionalDependencies: - '@types/node': 22.14.0 + '@types/node': 24.0.3 fsevents: 2.3.3 jiti: 1.21.0 terser: 5.39.2 @@ -12256,7 +12249,7 @@ snapshots: '@oozcitak/dom': 1.15.10 '@oozcitak/infra': 1.0.8 '@oozcitak/util': 8.3.8 - '@types/node': 22.14.0 + '@types/node': 24.0.3 js-yaml: 3.14.0 xmlbuilder@11.0.1: {} From 9c78f68b35505f29a382fb6830a729d077417586 Mon Sep 17 00:00:00 2001 From: Matthew McCormick Date: Wed, 18 Jun 2025 17:29:44 -0400 Subject: [PATCH 3/3] ci(playwright): add pnpm install for itk-wasm --- .github/workflows/playwright.yml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.github/workflows/playwright.yml b/.github/workflows/playwright.yml index 199e1d949..871d29c17 100644 --- a/.github/workflows/playwright.yml +++ b/.github/workflows/playwright.yml @@ -105,6 +105,10 @@ jobs: - name: Install Playwright Browsers run: pnpx playwright install --with-deps + - name: Install dependencies for itk-wasm + run: pnpm install + working-directory: packages/core/typescript/itk-wasm + - name: Build itk-wasm run: | pnpm run --aggregate-output --filter itk-wasm build