diff --git a/.github/workflows/pull-request-validation.yml b/.github/workflows/pull-request-validation.yml index 680f4b05fa..805d46b1e7 100644 --- a/.github/workflows/pull-request-validation.yml +++ b/.github/workflows/pull-request-validation.yml @@ -31,7 +31,7 @@ jobs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v6 - if: "env.skip-secure-feed != 'true'" name: Enable secure feed @@ -39,7 +39,7 @@ jobs: timeout-minutes: 8 # Terrapin normally finish within 4 minutes but sometimes hang - name: Use Node.js ${{ env.node-version }} - uses: actions/setup-node@v4 + uses: actions/setup-node@v6 with: node-version: ${{ env.node-version }} cache: npm @@ -74,7 +74,7 @@ jobs: - run: ls -l docker.zip - name: Upload Docker artifact - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v7 with: name: docker path: docker.zip @@ -85,7 +85,7 @@ jobs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v6 - if: "env.skip-secure-feed != 'true'" name: Enable secure feed @@ -93,7 +93,7 @@ jobs: timeout-minutes: 8 # Terrapin normally finish within 4 minutes but sometimes hang - name: Use Node.js ${{ env.node-version }} - uses: actions/setup-node@v4 + uses: actions/setup-node@v6 with: node-version: ${{ env.node-version }} cache: npm @@ -111,7 +111,7 @@ jobs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v6 - if: "env.skip-secure-feed != 'true'" name: Enable secure feed @@ -119,7 +119,7 @@ jobs: timeout-minutes: 8 # Terrapin normally finish within 4 minutes but sometimes hang - name: Use Node.js ${{ env.node-version }} - uses: actions/setup-node@v4 + uses: actions/setup-node@v6 with: node-version: ${{ env.node-version }} cache: npm @@ -154,7 +154,7 @@ jobs: - if: always() name: Upload test results - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v7 with: name: test-result path: | @@ -168,7 +168,7 @@ jobs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v6 - if: "env.skip-secure-feed != 'true'" name: Enable secure feed @@ -176,7 +176,7 @@ jobs: timeout-minutes: 8 # Terrapin normally finish within 4 minutes but sometimes hang - name: Use Node.js ${{ env.node-version }} - uses: actions/setup-node@v4 + uses: actions/setup-node@v6 with: node-version: ${{ env.node-version }} cache: npm @@ -221,12 +221,12 @@ jobs: timeout-minutes: 8 # Terrapin normally finish within 4 minutes but sometimes hang - name: Use Node.js ${{ env.node-version }} - uses: actions/setup-node@v4 + uses: actions/setup-node@v6 with: node-version: ${{ env.node-version }} - name: Download Docker artifact - uses: actions/download-artifact@v3 + uses: actions/download-artifact@v8 with: name: docker @@ -237,10 +237,10 @@ jobs: - run: npm clean-install - name: Run docker-compose build - run: docker-compose -f docker-compose-wsl2.yml build --build-arg REGISTRY=mcr.microsoft.com/mirror/docker/library + run: docker compose -f docker-compose-wsl2.yml build --build-arg REGISTRY=mcr.microsoft.com - name: Run docker-compose up - run: docker-compose -f docker-compose-wsl2.yml up --detach --scale chrome=2 + run: docker compose -f docker-compose-wsl2.yml up --detach --scale chrome=2 - name: Wait for Docker to be ready run: | @@ -269,7 +269,7 @@ jobs: - if: always() name: Print Docker logs - run: docker-compose -f docker-compose-wsl2.yml logs + run: docker compose -f docker-compose-wsl2.yml logs - if: always() name: Append ID to test result @@ -284,7 +284,7 @@ jobs: - if: always() name: Upload test results - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v7 with: name: test-result path: | @@ -295,7 +295,7 @@ jobs: - if: failure() name: Upload test snapshot diffs - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v7 with: name: test-snapshot-diff path: ./__tests__/__image_snapshots__/*/__diff_output__/* @@ -310,7 +310,7 @@ jobs: steps: - name: Download test results - uses: actions/download-artifact@v3 + uses: actions/download-artifact@v8 with: name: test-result diff --git a/CHANGELOG.md b/CHANGELOG.md index a5f6f8c216..4589509d93 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -24,6 +24,18 @@ Notes: web developers are advised to use [`~` (tilde range)](https://github.com/ ## [Unreleased] +## [4.18.1] - 2026-05-13 + +### Changed + +- Bumped some dependencies to the latest versions, by [@compulim](https://github.com/compulim) in PR [#XXX](https://github.com/microsoft/BotFramework-WebChat/pull/XXX) + - Production dependencies + - [`adaptivecards@3.0.6`](https://npmjs.com/package/adaptivecards) + - [`swiper@12.1.4`](https://npmjs.com/package/swiper) + - Transient dependencies + - [`form-data@4.0.6`](https://npmjs.com/package/form-data) + - [`handlebars@4.7.9`](https://npmjs.com/package/handlebars) + ## [4.18.0] - 2024-07-10 ### Added diff --git a/__tests__/__image_snapshots__/html/auto-scroll-after-send-js-auto-scroll-should-scroll-to-bottom-on-send-1-snap.png b/__tests__/__image_snapshots__/html/auto-scroll-after-send-js-auto-scroll-should-scroll-to-bottom-on-send-1-snap.png index 8543561d96..7adf6440c0 100644 Binary files a/__tests__/__image_snapshots__/html/auto-scroll-after-send-js-auto-scroll-should-scroll-to-bottom-on-send-1-snap.png and b/__tests__/__image_snapshots__/html/auto-scroll-after-send-js-auto-scroll-should-scroll-to-bottom-on-send-1-snap.png differ diff --git a/__tests__/__image_snapshots__/html/chat-adapter-direct-line-app-service-extension-js-direct-line-app-service-service-chat-adapter-should-connect-to-the-mock-bot-1-snap.png b/__tests__/__image_snapshots__/html/chat-adapter-direct-line-app-service-extension-js-direct-line-app-service-service-chat-adapter-should-connect-to-the-mock-bot-1-snap.png index 76ee65ac0e..3f3c251d51 100644 Binary files a/__tests__/__image_snapshots__/html/chat-adapter-direct-line-app-service-extension-js-direct-line-app-service-service-chat-adapter-should-connect-to-the-mock-bot-1-snap.png and b/__tests__/__image_snapshots__/html/chat-adapter-direct-line-app-service-extension-js-direct-line-app-service-service-chat-adapter-should-connect-to-the-mock-bot-1-snap.png differ diff --git a/__tests__/__image_snapshots__/html/conversation-start-properties-no-locale-is-sent-js-conversation-start-properties-with-no-locale-is-sent-should-get-hello-and-welcome-message-1-snap.png b/__tests__/__image_snapshots__/html/conversation-start-properties-no-locale-is-sent-js-conversation-start-properties-with-no-locale-is-sent-should-get-hello-and-welcome-message-1-snap.png index 60e934cd50..712f13b2b8 100644 Binary files a/__tests__/__image_snapshots__/html/conversation-start-properties-no-locale-is-sent-js-conversation-start-properties-with-no-locale-is-sent-should-get-hello-and-welcome-message-1-snap.png and b/__tests__/__image_snapshots__/html/conversation-start-properties-no-locale-is-sent-js-conversation-start-properties-with-no-locale-is-sent-should-get-hello-and-welcome-message-1-snap.png differ diff --git a/__tests__/__image_snapshots__/html/conversation-start-properties-send-en-us-js-conversation-start-properties-with-locale-of-en-us-should-get-hello-and-welcome-message-1-snap.png b/__tests__/__image_snapshots__/html/conversation-start-properties-send-en-us-js-conversation-start-properties-with-locale-of-en-us-should-get-hello-and-welcome-message-1-snap.png index 60e934cd50..70325cd939 100644 Binary files a/__tests__/__image_snapshots__/html/conversation-start-properties-send-en-us-js-conversation-start-properties-with-locale-of-en-us-should-get-hello-and-welcome-message-1-snap.png and b/__tests__/__image_snapshots__/html/conversation-start-properties-send-en-us-js-conversation-start-properties-with-locale-of-en-us-should-get-hello-and-welcome-message-1-snap.png differ diff --git a/__tests__/__image_snapshots__/html/conversation-start-properties-send-invalid-type-js-conversation-start-properties-with-locale-of-invalid-type-should-get-hello-and-welcome-message-1-snap.png b/__tests__/__image_snapshots__/html/conversation-start-properties-send-invalid-type-js-conversation-start-properties-with-locale-of-invalid-type-should-get-hello-and-welcome-message-1-snap.png index 60e934cd50..712f13b2b8 100644 Binary files a/__tests__/__image_snapshots__/html/conversation-start-properties-send-invalid-type-js-conversation-start-properties-with-locale-of-invalid-type-should-get-hello-and-welcome-message-1-snap.png and b/__tests__/__image_snapshots__/html/conversation-start-properties-send-invalid-type-js-conversation-start-properties-with-locale-of-invalid-type-should-get-hello-and-welcome-message-1-snap.png differ diff --git a/__tests__/__image_snapshots__/html/conversation-start-properties-send-non-existing-js-conversation-start-properties-with-non-existing-locale-should-get-hello-and-welcome-message-1-snap.png b/__tests__/__image_snapshots__/html/conversation-start-properties-send-non-existing-js-conversation-start-properties-with-non-existing-locale-should-get-hello-and-welcome-message-1-snap.png index 60e934cd50..7c662e4c4c 100644 Binary files a/__tests__/__image_snapshots__/html/conversation-start-properties-send-non-existing-js-conversation-start-properties-with-non-existing-locale-should-get-hello-and-welcome-message-1-snap.png and b/__tests__/__image_snapshots__/html/conversation-start-properties-send-non-existing-js-conversation-start-properties-with-non-existing-locale-should-get-hello-and-welcome-message-1-snap.png differ diff --git a/__tests__/__image_snapshots__/html/conversation-start-properties-send-non-iso-format-js-conversation-start-properties-with-non-iso-format-locale-should-get-hello-and-welcome-message-1-snap.png b/__tests__/__image_snapshots__/html/conversation-start-properties-send-non-iso-format-js-conversation-start-properties-with-non-iso-format-locale-should-get-hello-and-welcome-message-1-snap.png index 60e934cd50..3ee4fca9ac 100644 Binary files a/__tests__/__image_snapshots__/html/conversation-start-properties-send-non-iso-format-js-conversation-start-properties-with-non-iso-format-locale-should-get-hello-and-welcome-message-1-snap.png and b/__tests__/__image_snapshots__/html/conversation-start-properties-send-non-iso-format-js-conversation-start-properties-with-non-iso-format-locale-should-get-hello-and-welcome-message-1-snap.png differ diff --git a/__tests__/__image_snapshots__/html/conversation-start-properties-send-zh-cn-js-conversation-start-properties-with-locale-of-zh-cn-should-get-hello-and-welcome-in-simplified-chinese-1-snap.png b/__tests__/__image_snapshots__/html/conversation-start-properties-send-zh-cn-js-conversation-start-properties-with-locale-of-zh-cn-should-get-hello-and-welcome-in-simplified-chinese-1-snap.png index 029b279549..3bd852d2df 100644 Binary files a/__tests__/__image_snapshots__/html/conversation-start-properties-send-zh-cn-js-conversation-start-properties-with-locale-of-zh-cn-should-get-hello-and-welcome-in-simplified-chinese-1-snap.png and b/__tests__/__image_snapshots__/html/conversation-start-properties-send-zh-cn-js-conversation-start-properties-with-locale-of-zh-cn-should-get-hello-and-welcome-in-simplified-chinese-1-snap.png differ diff --git a/__tests__/__image_snapshots__/html/hooks-use-text-box-js-use-text-box-should-work-properly-1-snap.png b/__tests__/__image_snapshots__/html/hooks-use-text-box-js-use-text-box-should-work-properly-1-snap.png index c8f71ff439..a009002347 100644 Binary files a/__tests__/__image_snapshots__/html/hooks-use-text-box-js-use-text-box-should-work-properly-1-snap.png and b/__tests__/__image_snapshots__/html/hooks-use-text-box-js-use-text-box-should-work-properly-1-snap.png differ diff --git a/__tests__/__image_snapshots__/html/hooks-use-text-box-js-use-text-box-should-work-properly-2-snap.png b/__tests__/__image_snapshots__/html/hooks-use-text-box-js-use-text-box-should-work-properly-2-snap.png index c5bbd56334..83765849fa 100644 Binary files a/__tests__/__image_snapshots__/html/hooks-use-text-box-js-use-text-box-should-work-properly-2-snap.png and b/__tests__/__image_snapshots__/html/hooks-use-text-box-js-use-text-box-should-work-properly-2-snap.png differ diff --git a/__tests__/__image_snapshots__/chrome-docker/card-action-middleware-js-card-action-open-url-1-snap.png b/__tests__/__image_snapshots__/html/open-url-js-card-action-open-url-html-1-snap.png similarity index 100% rename from __tests__/__image_snapshots__/chrome-docker/card-action-middleware-js-card-action-open-url-1-snap.png rename to __tests__/__image_snapshots__/html/open-url-js-card-action-open-url-html-1-snap.png diff --git a/__tests__/__image_snapshots__/html/scroll-to-end-button-visibility-js-scroll-to-end-button-should-show-and-hide-properly-1-snap.png b/__tests__/__image_snapshots__/html/scroll-to-end-button-visibility-js-scroll-to-end-button-should-show-and-hide-properly-1-snap.png index 69ef126a81..3afb91d1c6 100644 Binary files a/__tests__/__image_snapshots__/html/scroll-to-end-button-visibility-js-scroll-to-end-button-should-show-and-hide-properly-1-snap.png and b/__tests__/__image_snapshots__/html/scroll-to-end-button-visibility-js-scroll-to-end-button-should-show-and-hide-properly-1-snap.png differ diff --git a/__tests__/__image_snapshots__/html/scroll-to-end-button-visibility-js-scroll-to-end-button-should-show-and-hide-properly-2-snap.png b/__tests__/__image_snapshots__/html/scroll-to-end-button-visibility-js-scroll-to-end-button-should-show-and-hide-properly-2-snap.png index c6527b33a2..043f09ea43 100644 Binary files a/__tests__/__image_snapshots__/html/scroll-to-end-button-visibility-js-scroll-to-end-button-should-show-and-hide-properly-2-snap.png and b/__tests__/__image_snapshots__/html/scroll-to-end-button-visibility-js-scroll-to-end-button-should-show-and-hide-properly-2-snap.png differ diff --git a/__tests__/__image_snapshots__/html/scroll-to-end-button-visibility-js-scroll-to-end-button-should-show-and-hide-properly-3-snap.png b/__tests__/__image_snapshots__/html/scroll-to-end-button-visibility-js-scroll-to-end-button-should-show-and-hide-properly-3-snap.png index 7036d9dd48..6b1caabef4 100644 Binary files a/__tests__/__image_snapshots__/html/scroll-to-end-button-visibility-js-scroll-to-end-button-should-show-and-hide-properly-3-snap.png and b/__tests__/__image_snapshots__/html/scroll-to-end-button-visibility-js-scroll-to-end-button-should-show-and-hide-properly-3-snap.png differ diff --git a/__tests__/__image_snapshots__/html/scroll-to-end-button-visibility-js-scroll-to-end-button-should-show-and-hide-properly-4-snap.png b/__tests__/__image_snapshots__/html/scroll-to-end-button-visibility-js-scroll-to-end-button-should-show-and-hide-properly-4-snap.png index c8f71ff439..3519fcb555 100644 Binary files a/__tests__/__image_snapshots__/html/scroll-to-end-button-visibility-js-scroll-to-end-button-should-show-and-hide-properly-4-snap.png and b/__tests__/__image_snapshots__/html/scroll-to-end-button-visibility-js-scroll-to-end-button-should-show-and-hide-properly-4-snap.png differ diff --git a/__tests__/__image_snapshots__/chrome-docker/card-action-middleware-js-card-action-signin-1-snap.png b/__tests__/__image_snapshots__/html/sign-in-js-card-action-sign-in-html-1-snap.png similarity index 100% rename from __tests__/__image_snapshots__/chrome-docker/card-action-middleware-js-card-action-signin-1-snap.png rename to __tests__/__image_snapshots__/html/sign-in-js-card-action-sign-in-html-1-snap.png diff --git a/__tests__/__image_snapshots__/chrome-docker/card-action-middleware-js-card-action-signin-when-direct-line-get-session-id-is-falsy-1-snap.png b/__tests__/__image_snapshots__/html/sign-in-no-get-session-id-js-card-action-sign-in-no-get-session-id-html-1-snap.png similarity index 100% rename from __tests__/__image_snapshots__/chrome-docker/card-action-middleware-js-card-action-signin-when-direct-line-get-session-id-is-falsy-1-snap.png rename to __tests__/__image_snapshots__/html/sign-in-no-get-session-id-js-card-action-sign-in-no-get-session-id-html-1-snap.png diff --git a/__tests__/__image_snapshots__/chrome-docker/video-js-video-1-snap.png b/__tests__/__image_snapshots__/html/video-youtube-js-1-snap.png similarity index 100% rename from __tests__/__image_snapshots__/chrome-docker/video-js-video-1-snap.png rename to __tests__/__image_snapshots__/html/video-youtube-js-1-snap.png diff --git a/__tests__/cardActionMiddleware.js b/__tests__/cardActionMiddleware.js deleted file mode 100644 index ceaafdfa55..0000000000 --- a/__tests__/cardActionMiddleware.js +++ /dev/null @@ -1,156 +0,0 @@ -import { By } from 'selenium-webdriver'; - -import { imageSnapshotOptions, timeouts } from './constants.json'; - -import allOutgoingActivitiesSent from './setup/conditions/allOutgoingActivitiesSent'; -import getTranscript from './setup/elements/getTranscript'; -import minNumActivitiesShown from './setup/conditions/minNumActivitiesShown.js'; -import suggestedActionsShown from './setup/conditions/suggestedActionsShown'; -import uiConnected from './setup/conditions/uiConnected'; - -// selenium-webdriver API doc: -// https://seleniumhq.github.io/selenium/docs/api/javascript/module/selenium-webdriver/index_exports_WebDriver.html - -jest.setTimeout(timeouts.test); - -test('card action "openUrl"', async () => { - const { driver, pageObjects } = await setupWebDriver({ - props: { - cardActionMiddleware: - ({ dispatch }) => - next => - ({ cardAction }) => { - if (cardAction.type === 'openUrl') { - dispatch({ - type: 'WEB_CHAT/SEND_MESSAGE', - payload: { - text: `Navigating to ${cardAction.value}` - } - }); - } else { - return next(cardAction); - } - } - } - }); - - await driver.wait(uiConnected(), timeouts.directLine); - await pageObjects.sendMessageViaSendBox('card-actions', { waitForSend: true }); - - await driver.wait(suggestedActionsShown(), timeouts.directLine); - - const openUrlButton = await driver.findElement(By.css('[role="form"] ul > li:first-child button')); - - await openUrlButton.click(); - await driver.wait(minNumActivitiesShown(4), timeouts.directLine); - await driver.wait(allOutgoingActivitiesSent(), timeouts.directLine); - - const base64PNG = await driver.takeScreenshot(); - - expect(base64PNG).toMatchImageSnapshot(imageSnapshotOptions); -}); - -test('card action "signin"', async () => { - const { driver, pageObjects } = await setupWebDriver({ - props: { - cardActionMiddleware: - ({ dispatch }) => - next => - ({ cardAction, getSignInUrl }) => { - if (cardAction.type === 'signin') { - getSignInUrl().then(url => { - dispatch({ - type: 'WEB_CHAT/SEND_MESSAGE', - payload: { - text: `Signing into ${new URL(url).host}` - } - }); - }); - } else { - return next(cardAction); - } - } - }, - useProductionBot: true - }); - - await driver.wait(uiConnected(), timeouts.directLine); - await pageObjects.sendMessageViaSendBox('oauth', { waitForSend: true }); - - await driver.wait(minNumActivitiesShown(2), timeouts.directLine); - - const transcript = await getTranscript(driver); - const openUrlButton = await transcript.findElement(By.css('.webchat__bubble__content button')); - - await openUrlButton.click(); - await driver.wait(minNumActivitiesShown(4), timeouts.directLine); - await driver.wait(allOutgoingActivitiesSent(), timeouts.directLine); - - // When the "Sign in" button is clicked, the focus move to it, need to blur it. - await driver.executeScript(() => { - const { activeElement } = document; - - activeElement && activeElement.blur(); - }); - - const base64PNG = await driver.takeScreenshot(); - - expect(base64PNG).toMatchImageSnapshot(imageSnapshotOptions); -}); - -test('card action "signin" when directLine.getSessionId is falsy', async () => { - const { driver, pageObjects } = await setupWebDriver({ - disableNoMagicCode: true, - props: { - cardActionMiddleware: - ({ dispatch }) => - next => - ({ cardAction, getSignInUrl }) => { - if (cardAction.type === 'signin') { - Promise.resolve(getSignInUrl()).then(url => { - dispatch({ - type: 'WEB_CHAT/SEND_MESSAGE', - payload: { - text: `Signing into ${new URL(url).host}` - } - }); - }); - } else { - return next(cardAction); - } - } - }, - useProductionBot: true - }); - - await driver.wait(uiConnected(), timeouts.directLine); - await pageObjects.sendMessageViaSendBox('oauth', { waitForSend: true }); - - await driver.wait(minNumActivitiesShown(2), timeouts.directLine); - - const transcript = await getTranscript(driver); - const openUrlButton = await transcript.findElement(By.css('.webchat__bubble__content button')); - - await openUrlButton.click(); - await driver.wait(minNumActivitiesShown(4), timeouts.directLine); - await driver.wait(allOutgoingActivitiesSent(), timeouts.directLine); - - // When the "Sign in" button is clicked, the focus move to it, need to blur it. - await driver.executeScript(() => { - const { activeElement } = document; - - activeElement && activeElement.blur(); - }); - - const base64PNG = await driver.takeScreenshot(); - - expect(base64PNG).toMatchImageSnapshot(imageSnapshotOptions); - - await expect(pageObjects.getConsoleErrors()).resolves.toEqual([]); - - expect( - (await pageObjects.getConsoleWarnings()).includes( - 'botframework-webchat: OAuth is not supported on this Direct Line adapter.' - ) - ).toBe(true); -}); diff --git a/__tests__/html/accessibility.adaptiveCard.disabled.inputs.html b/__tests__/html/accessibility.adaptiveCard.disabled.inputs.html index f327db9937..18163e3b72 100644 --- a/__tests__/html/accessibility.adaptiveCard.disabled.inputs.html +++ b/__tests__/html/accessibility.adaptiveCard.disabled.inputs.html @@ -77,7 +77,6 @@ 'DOWN', // What color do you want? Red 'DOWN', // Green 'TAB', - 'TAB', // AC 2.11: Checkbox group container has tabindex="0" 'SPACE', // What colors do you want? Red 'TAB', 'SPACE', // Green diff --git a/__tests__/html/assets/esm/speech/MockedSpeechSynthesis.js b/__tests__/html/assets/esm/speech/MockedSpeechSynthesis.js new file mode 100644 index 0000000000..0efc7e5d8c --- /dev/null +++ b/__tests__/html/assets/esm/speech/MockedSpeechSynthesis.js @@ -0,0 +1,133 @@ +import { EventTargetProperties } from 'https://esm.sh/event-target-properties'; +import SpeechSynthesisEvent from './MockedSpeechSynthesisEvent.js'; +import SpeechSynthesisVoice from './MockedSpeechSynthesisVoice.js'; + +export default class SpeechSynthesis extends EventTarget { + constructor() { + super(); + + this.#eventTargetProperties = new EventTargetProperties(this); + } + + /** @type {SpeechSynthesisUtterance} */ + #currentUtterance; + /** @type {EventTargetProperties} */ + #eventTargetProperties; + /** @type {boolean} */ + #paused = false; + // #pending = false; + /** @type {SpeechSynthesisUtterance[]} */ + #queue = []; + /** @type {boolean} */ + #speaking = false; + + get onvoiceschanged() { + return this.#eventTargetProperties.getProperty('voiceschanged'); + } + + set onvoiceschanged(value) { + this.#eventTargetProperties.setProperty('voiceschanged', value); + } + + /** @type {boolean} */ + get paused() { + return this.#paused; + } + + /** @type {boolean} */ + get pending() { + return !!this.#queue.length; + } + + /** @type {boolean} */ + get speaking() { + return !this.paused && this.#speaking; + } + + cancel() { + this.#paused = false; + this.#speaking = false; + this.#queue.splice(0); + + this.#currentUtterance?.dispatchEvent(new SpeechSynthesisEvent('end', { utterance: this.#currentUtterance })); + } + + getVoices() { + return Object.freeze([ + SpeechSynthesisVoice.from({ + default: true, + lang: 'en-US', + localService: true, + name: 'Mock Voice (en-US)', + voiceURI: 'mock://web-speech/voice/en-US' + }), + SpeechSynthesisVoice.from({ + default: false, + lang: 'zh-YUE', + localService: true, + name: 'Mock Voice (zh-YUE)', + voiceURI: 'mock://web-speech/voice/zh-YUE' + }) + ]); + } + + pause() { + if (this.#paused) { + return; + } + + this.#paused = true; + + this.#currentUtterance?.dispatchEvent(new SpeechSynthesisEvent('pause', { utterance: this.#currentUtterance })); + } + + resume() { + if (!this.#paused) { + return; + } + + this.#paused = false; + + if (this.#currentUtterance) { + this.#currentUtterance.dispatchEvent(new SpeechSynthesisEvent('resume', { utterance: this.#currentUtterance })); + } else { + this.#next(); + } + } + + speak(/** @type {SpeechSynthesisUtterance} */ utterance) { + this.#queue.push(/** @type {SpeechSynthesisUtterance} */ utterance); + + !this.#paused && !this.#speaking && this.#next(); + } + + #next() { + if (this.#paused) { + throw new Error('Should not call #next() when it is paused.'); + } + + this.#currentUtterance = this.#queue.shift(); + + if (!this.#currentUtterance) { + this.#paused = false; + this.#speaking = false; + + return; + } + + this.#speaking = true; + + this.#currentUtterance.addEventListener('end', () => this.#next(), { once: true }); + this.#currentUtterance.addEventListener( + 'error', + () => { + this.#paused = false; + this.#speaking = false; + this.#queue.splice(0); + }, + { once: true } + ); + + this.#currentUtterance.dispatchEvent(new SpeechSynthesisEvent('start', { utterance: this.#currentUtterance })); + } +} diff --git a/__tests__/html/assets/esm/speech/MockedSpeechSynthesisErrorEvent.js b/__tests__/html/assets/esm/speech/MockedSpeechSynthesisErrorEvent.js new file mode 100644 index 0000000000..30c7db7a6e --- /dev/null +++ b/__tests__/html/assets/esm/speech/MockedSpeechSynthesisErrorEvent.js @@ -0,0 +1,21 @@ +import SpeechSynthesisEvent from './MockedSpeechSynthesisEvent.js'; + +export default class SpeechSynthesisErrorEvent extends SpeechSynthesisEvent { + constructor( + /** @type {string} */ + type, + /** @type {EventInitDict} */ + eventInitDict + ) { + super(type, eventInitDict); + + this.#error = eventInitDict.error; + } + + /** @type {unknown} */ + #error; + + get error() { + return this.#error; + } +} diff --git a/__tests__/html/assets/esm/speech/MockedSpeechSynthesisEvent.js b/__tests__/html/assets/esm/speech/MockedSpeechSynthesisEvent.js new file mode 100644 index 0000000000..bd99d28cd0 --- /dev/null +++ b/__tests__/html/assets/esm/speech/MockedSpeechSynthesisEvent.js @@ -0,0 +1,47 @@ +export default class SpeechSynthesisEvent extends Event { + constructor( + /** @type {string} */ + type, + /** @type {EventInitDict} */ + eventInitDict + ) { + super(type, eventInitDict); + + this.#charIndex = eventInitDict.charIndex || 0; + this.#charLength = eventInitDict.charLength || 0; + this.#elapsedTime = eventInitDict.elapsedTime || 0; + this.#name = eventInitDict.name || ''; + this.#utterance = eventInitDict.utterance; + } + + /** @type {number} */ + #charIndex; + /** @type {number} */ + #charLength; + /** @type {number} */ + #elapsedTime; + /** @type {string} */ + #name; + /** @type {SpeechSynthesisUtterance | undefined} */ + #utterance; + + get charIndex() { + return this.#charIndex; + } + + get charLength() { + return this.#charLength; + } + + get elapsedTime() { + return this.#elapsedTime; + } + + get name() { + return this.#name; + } + + get utterance() { + return this.#utterance; + } +} diff --git a/__tests__/html/assets/esm/speech/MockedSpeechSynthesisUtterance.js b/__tests__/html/assets/esm/speech/MockedSpeechSynthesisUtterance.js new file mode 100644 index 0000000000..d8c3bb423d --- /dev/null +++ b/__tests__/html/assets/esm/speech/MockedSpeechSynthesisUtterance.js @@ -0,0 +1,81 @@ +import { EventTargetProperties } from 'https://unpkg.com/event-target-properties@latest/dist/event-target-properties.mjs'; + +export default class SpeechSynthesisUtterance extends EventTarget { + constructor(text) { + super(); + + this.#eventTargetProperties = new EventTargetProperties(this); + this.text = text || ''; + } + + #eventTargetProperties; + + /** @type {string} */ + lang; + /** @type {number} */ + pitch; + /** @type {number} */ + rate; + /** @type {string} */ + text; + /** @type {any} */ + voice; + /** @type {number} */ + volume; + + get onboundary() { + return this.#eventTargetProperties.getProperty('boundary'); + } + + set onboundary(value) { + this.#eventTargetProperties.setProperty('boundary', value); + } + + get onend() { + return this.#eventTargetProperties.getProperty('end'); + } + + set onend(value) { + this.#eventTargetProperties.setProperty('end', value); + } + + get onerror() { + return this.#eventTargetProperties.getProperty('error'); + } + + set onerror(value) { + this.#eventTargetProperties.setProperty('error', value); + } + + get onmark() { + return this.#eventTargetProperties.getProperty('mark'); + } + + set onmark(value) { + this.#eventTargetProperties.setProperty('mark', value); + } + + get onpause() { + return this.#eventTargetProperties.getProperty('pause'); + } + + set onpause(value) { + this.#eventTargetProperties.setProperty('pause', value); + } + + get onresume() { + return this.#eventTargetProperties.getProperty('resume'); + } + + set onresume(value) { + this.#eventTargetProperties.setProperty('resume', value); + } + + get onstart() { + return this.#eventTargetProperties.getProperty('start'); + } + + set onstart(value) { + this.#eventTargetProperties.setProperty('start', value); + } +} diff --git a/__tests__/html/assets/esm/speech/MockedSpeechSynthesisVoice.js b/__tests__/html/assets/esm/speech/MockedSpeechSynthesisVoice.js new file mode 100644 index 0000000000..ce48b722ac --- /dev/null +++ b/__tests__/html/assets/esm/speech/MockedSpeechSynthesisVoice.js @@ -0,0 +1,48 @@ +export default class SpeechSynthesisVoice { + /** @type {boolean} */ + #default = false; + + /** @type {string} */ + #lang = ''; + + /** @type {boolean} */ + #localService = false; + + /** @type {string} */ + #name = ''; + + /** @type {string} */ + #voiceURI = ''; + + get default() { + return this.#default; + } + + get lang() { + return this.#lang; + } + + get localService() { + return this.#localService; + } + + get name() { + return this.#name; + } + + get voiceURI() { + return this.#voiceURI; + } + + static from(init) { + const voice = new SpeechSynthesisVoice(); + + voice.#default = init.default; + voice.#lang = init.lang; + voice.#localService = init.localService; + voice.#name = init.name; + voice.#voiceURI = init.voiceURI; + + return voice; + } +} diff --git a/__tests__/html/assets/esm/speech/speechPageObjects.js b/__tests__/html/assets/esm/speech/speechPageObjects.js new file mode 100644 index 0000000000..e399cd3376 --- /dev/null +++ b/__tests__/html/assets/esm/speech/speechPageObjects.js @@ -0,0 +1,223 @@ +/* eslint-disable no-empty-function */ +/* eslint-env browser */ + +import { + SpeechGrammarList, + SpeechRecognition, + SpeechRecognitionAlternative, + SpeechRecognitionErrorEvent, + SpeechRecognitionEvent, + SpeechRecognitionResult, + SpeechRecognitionResultList +} from 'react-dictate-button/internal'; +import { fn, spyOn } from 'jest-mock'; +import SpeechSynthesis from './MockedSpeechSynthesis.js'; +import SpeechSynthesisErrorEvent from './MockedSpeechSynthesisErrorEvent.js'; +import SpeechSynthesisEvent from './MockedSpeechSynthesisEvent.js'; +import SpeechSynthesisUtterance from './MockedSpeechSynthesisUtterance.js'; +import SpeechSynthesisVoice from './MockedSpeechSynthesisVoice.js'; + +function createWebSpeechPonyfill() { + const speechSynthesis = new SpeechSynthesis(); + + return { + SpeechGrammarList, + SpeechRecognition: fn().mockImplementation(() => { + const speechRecognition = new SpeechRecognition(); + + spyOn(speechRecognition, 'abort'); + spyOn(speechRecognition, 'start'); + + return speechRecognition; + }), + speechSynthesis, + SpeechSynthesisErrorEvent, + SpeechSynthesisEvent, + SpeechSynthesisUtterance, + SpeechSynthesisVoice + }; +} + +/** + * @deprecated + */ +async function actSpeakOnce({ speechSynthesis }, actor, speakActor) { + let lastUtterance; + + spyOn(speechSynthesis, 'speak').mockImplementationOnce(async utterance => { + lastUtterance = utterance; + + utterance.dispatchEvent(new SpeechSynthesisEvent('start', { utterance })); + + await speakActor?.(utterance); + + utterance.dispatchEvent(new SpeechSynthesisEvent('end', { utterance })); + }); + + await actor(); + + return lastUtterance; +} + +async function actSpeak({ speechSynthesis }, actor, speakActor) { + const utterances = []; + + spyOn(speechSynthesis, 'speak').mockImplementation(async utterance => { + utterances.push(utterance); + + utterance.dispatchEvent(new SpeechSynthesisEvent('start', { utterance })); + + try { + await speakActor?.(utterance); + } catch (error) { + utterance.dispatchEvent(new SpeechSynthesisErrorEvent('error', { error, utterance })); + + return; + } + + utterance.dispatchEvent(new SpeechSynthesisEvent('end', { utterance })); + }); + + await actor(); + + return Object.freeze(utterances); +} + +function actRecognizeOnce(ponyfill, actor, recognizeActor) { + // eslint-disable-next-line no-async-promise-executor + return new Promise(async resolve => { + const OriginalSpeechRecognition = ponyfill.SpeechRecognition; + + spyOn(ponyfill, 'SpeechRecognition').mockImplementationOnce(() => { + const speechRecognition = new OriginalSpeechRecognition(); + + let started = false; + let audioStarted = false; + let soundStarted = false; + let speechStarted = false; + + const end = () => { + started && speechRecognition.dispatchEvent(new Event('end')); + started = false; + }; + + const audioEnd = () => { + audioStarted && speechRecognition.dispatchEvent(new Event('audioend')); + audioStarted = false; + }; + + const soundEnd = () => { + soundStarted && speechRecognition.dispatchEvent(new Event('soundend')); + soundStarted = false; + }; + + const speechEnd = () => { + speechStarted && speechRecognition.dispatchEvent(new Event('speechend')); + speechStarted = false; + }; + + const start = () => { + started || speechRecognition.dispatchEvent(new Event('start')); + started = true; + }; + + const audioStart = () => { + audioStarted || speechRecognition.dispatchEvent(new Event('audiostart')); + audioStarted = true; + }; + + const soundStart = () => { + soundStarted || speechRecognition.dispatchEvent(new Event('soundstart')); + soundStarted = true; + }; + + const speechStart = () => { + speechStarted || speechRecognition.dispatchEvent(new Event('speechstart')); + speechStarted = true; + }; + + const error = reason => { + speechRecognition.dispatchEvent(new SpeechRecognitionErrorEvent('error', { error: reason })); + started = true; + }; + + const result = text => { + speechRecognition.dispatchEvent( + new SpeechRecognitionEvent('result', { + results: new SpeechRecognitionResultList( + new SpeechRecognitionResult(new SpeechRecognitionAlternative(1.0, text)) + ) + }) + ); + }; + + const finalizedResult = text => { + speechRecognition.dispatchEvent( + new SpeechRecognitionEvent('result', { + results: new SpeechRecognitionResultList( + SpeechRecognitionResult.fromFinalized(new SpeechRecognitionAlternative(1.0, text)) + ) + }) + ); + }; + + spyOn(speechRecognition, 'abort').mockImplementationOnce(() => { + speechEnd(); + soundEnd(); + audioEnd(); + error('aborted'); + end(); + }); + + spyOn(speechRecognition, 'start').mockImplementationOnce(async () => { + if (typeof recognizeActor === 'string') { + start(); + audioStart(); + soundStart(); + speechStart(); + finalizedResult(recognizeActor); + speechEnd(); + soundEnd(); + audioEnd(); + end(); + } else { + await recognizeActor?.({ + start, + audioStart, + soundStart, + speechStart, + speechEnd, + soundEnd, + audioEnd, + error, + end, + result, + finalizedResult + }); + } + + resolve(); + }); + + return speechRecognition; + }); + + await actor(); + }); +} + +async function sendMessageViaMicrophone(speechPonyfill, text) { + const clickMicrophoneButton = () => host.click(document.querySelector(`.webchat__microphone-button__button`)); + + await actRecognizeOnce( + speechPonyfill, + speechPonyfill.speechSynthesis + ? () => + // WHEN: Microphone button is clicked and synthesized empty utterace for user gesture requirement. + actSpeakOnce(speechPonyfill, clickMicrophoneButton) + : clickMicrophoneButton, + text + ); +} + +export { actRecognizeOnce, actSpeak, actSpeakOnce, createWebSpeechPonyfill, sendMessageViaMicrophone }; diff --git a/__tests__/html/basic/sendBox.trimOutgoing.html b/__tests__/html/basic/sendBox.trimOutgoing.html new file mode 100644 index 0000000000..ebc5112c4a --- /dev/null +++ b/__tests__/html/basic/sendBox.trimOutgoing.html @@ -0,0 +1,55 @@ + + + + + + + + + + + +
+ + + diff --git a/__tests__/html/basic/sendBox.trimOutgoing.js b/__tests__/html/basic/sendBox.trimOutgoing.js new file mode 100644 index 0000000000..f71a4e97d3 --- /dev/null +++ b/__tests__/html/basic/sendBox.trimOutgoing.js @@ -0,0 +1,3 @@ +/** @jest-environment ./packages/test/harness/src/host/jest/WebDriverEnvironment.js */ + +test('basic/sendBox.trimOutgoing.html', () => runHTML('basic/sendBox.trimOutgoing.html')); diff --git a/__tests__/html/cardAction/openURL.html b/__tests__/html/cardAction/openURL.html new file mode 100644 index 0000000000..285826a394 --- /dev/null +++ b/__tests__/html/cardAction/openURL.html @@ -0,0 +1,52 @@ + + + + + + + + + +
+ + + diff --git a/__tests__/html/cardAction/openURL.js b/__tests__/html/cardAction/openURL.js new file mode 100644 index 0000000000..5657eea186 --- /dev/null +++ b/__tests__/html/cardAction/openURL.js @@ -0,0 +1,3 @@ +/** @jest-environment ./packages/test/harness/src/host/jest/WebDriverEnvironment.js */ + +test('cardAction/openURL.html', () => runHTML('cardAction/openURL.html')); diff --git a/__tests__/html/cardAction/signIn.html b/__tests__/html/cardAction/signIn.html new file mode 100644 index 0000000000..c5e3c98985 --- /dev/null +++ b/__tests__/html/cardAction/signIn.html @@ -0,0 +1,60 @@ + + + + + + + + + +
+ + + diff --git a/__tests__/html/cardAction/signIn.js b/__tests__/html/cardAction/signIn.js new file mode 100644 index 0000000000..ca2a68a432 --- /dev/null +++ b/__tests__/html/cardAction/signIn.js @@ -0,0 +1,3 @@ +/** @jest-environment ./packages/test/harness/src/host/jest/WebDriverEnvironment.js */ + +test('cardAction/signIn.html', () => runHTML('cardAction/signIn.html')); diff --git a/__tests__/html/cardAction/signIn.noGetSessionId.html b/__tests__/html/cardAction/signIn.noGetSessionId.html new file mode 100644 index 0000000000..ffe5293ead --- /dev/null +++ b/__tests__/html/cardAction/signIn.noGetSessionId.html @@ -0,0 +1,75 @@ + + + + + + + + + +
+ + + diff --git a/__tests__/html/cardAction/signIn.noGetSessionId.js b/__tests__/html/cardAction/signIn.noGetSessionId.js new file mode 100644 index 0000000000..03fa4d8be9 --- /dev/null +++ b/__tests__/html/cardAction/signIn.noGetSessionId.js @@ -0,0 +1,3 @@ +/** @jest-environment ./packages/test/harness/src/host/jest/WebDriverEnvironment.js */ + +test('cardAction/signIn.noGetSessionId.html', () => runHTML('cardAction/signIn.noGetSessionId.html')); diff --git a/__tests__/html/chatAdapter.directLineAppServiceExtension.html b/__tests__/html/chatAdapter.directLineAppServiceExtension.html index 3f50ef624a..af4f9cb00d 100644 --- a/__tests__/html/chatAdapter.directLineAppServiceExtension.html +++ b/__tests__/html/chatAdapter.directLineAppServiceExtension.html @@ -1,4 +1,4 @@ - + @@ -10,19 +10,18 @@
+ + + + +
+ + + + diff --git a/__tests__/html/speech/synthesis/adaptiveCards.js b/__tests__/html/speech/synthesis/adaptiveCards.js new file mode 100644 index 0000000000..cd2b0afb05 --- /dev/null +++ b/__tests__/html/speech/synthesis/adaptiveCards.js @@ -0,0 +1,3 @@ +/** @jest-environment ./packages/test/harness/src/host/jest/WebDriverEnvironment.js */ + +test('speech/synthesis/adaptiveCards.html', () => runHTML('speech/synthesis/adaptiveCards.html')); diff --git a/__tests__/html/speech/synthesis/consecutiveMessages.html b/__tests__/html/speech/synthesis/consecutiveMessages.html new file mode 100644 index 0000000000..3f2276f801 --- /dev/null +++ b/__tests__/html/speech/synthesis/consecutiveMessages.html @@ -0,0 +1,70 @@ + + + + + + + + + +
+ + + + diff --git a/__tests__/html/speech/synthesis/consecutiveMessages.js b/__tests__/html/speech/synthesis/consecutiveMessages.js new file mode 100644 index 0000000000..44eee4f2ca --- /dev/null +++ b/__tests__/html/speech/synthesis/consecutiveMessages.js @@ -0,0 +1,3 @@ +/** @jest-environment ./packages/test/harness/src/host/jest/WebDriverEnvironment.js */ + +test('speech/synthesis/consecutiveMessages.html', () => runHTML('speech/synthesis/consecutiveMessages.html')); diff --git a/__tests__/html/speech/synthesis/disableSynthesis.html b/__tests__/html/speech/synthesis/disableSynthesis.html new file mode 100644 index 0000000000..061cf768b4 --- /dev/null +++ b/__tests__/html/speech/synthesis/disableSynthesis.html @@ -0,0 +1,81 @@ + + + + + + + + + +
+ + + + diff --git a/__tests__/html/speech/synthesis/disableSynthesis.js b/__tests__/html/speech/synthesis/disableSynthesis.js new file mode 100644 index 0000000000..2deb0303b9 --- /dev/null +++ b/__tests__/html/speech/synthesis/disableSynthesis.js @@ -0,0 +1,3 @@ +/** @jest-environment ./packages/test/harness/src/host/jest/WebDriverEnvironment.js */ + +test('speech/synthesis/disableSynthesis.html', () => runHTML('speech/synthesis/disableSynthesis.html')); diff --git a/__tests__/html/speech/synthesis/startRecognitionAfterFailedSynthesis.html b/__tests__/html/speech/synthesis/startRecognitionAfterFailedSynthesis.html new file mode 100644 index 0000000000..ba0ec26e8d --- /dev/null +++ b/__tests__/html/speech/synthesis/startRecognitionAfterFailedSynthesis.html @@ -0,0 +1,89 @@ + + + + + + + + + +
+ + + + diff --git a/__tests__/html/speech/synthesis/startRecognitionAfterFailedSynthesis.js b/__tests__/html/speech/synthesis/startRecognitionAfterFailedSynthesis.js new file mode 100644 index 0000000000..085fde28a2 --- /dev/null +++ b/__tests__/html/speech/synthesis/startRecognitionAfterFailedSynthesis.js @@ -0,0 +1,4 @@ +/** @jest-environment ./packages/test/harness/src/host/jest/WebDriverEnvironment.js */ + +test('speech/synthesis/startRecognitionAfterFailedSynthesis.html', () => + runHTML('speech/synthesis/startRecognitionAfterFailedSynthesis.html')); diff --git a/__tests__/html/speech/synthesis/stopSynthesisOnMicrophoneButton.html b/__tests__/html/speech/synthesis/stopSynthesisOnMicrophoneButton.html new file mode 100644 index 0000000000..0652ee2753 --- /dev/null +++ b/__tests__/html/speech/synthesis/stopSynthesisOnMicrophoneButton.html @@ -0,0 +1,92 @@ + + + + + + + + + +
+ + + + diff --git a/__tests__/html/speech/synthesis/stopSynthesisOnMicrophoneButton.js b/__tests__/html/speech/synthesis/stopSynthesisOnMicrophoneButton.js new file mode 100644 index 0000000000..05da214a3e --- /dev/null +++ b/__tests__/html/speech/synthesis/stopSynthesisOnMicrophoneButton.js @@ -0,0 +1,4 @@ +/** @jest-environment ./packages/test/harness/src/host/jest/WebDriverEnvironment.js */ + +test('speech/synthesis/stopSynthesisOnMicrophoneButton.html', () => + runHTML('speech/synthesis/stopSynthesisOnMicrophoneButton.html')); diff --git a/__tests__/html/speechRecognition.simple.js b/__tests__/html/speechRecognition.simple.js index 1361506b36..f14d1c2edd 100644 --- a/__tests__/html/speechRecognition.simple.js +++ b/__tests__/html/speechRecognition.simple.js @@ -25,7 +25,7 @@ describe.each([ { useDirectLineSpeech: true, useHostname: true, useSubscriptionKey: true } ] ])('speech recognition using %s', (_, { useSubscriptionKey, useDirectLineSpeech, useHostname }) => { - test.nightly('should recognize "Hello, World!".', async () => { + test.skip('should recognize "Hello, World!".', async () => { if (!useDirectLineSpeech && !COGNITIVE_SERVICES_SUBSCRIPTION_KEY) { throw new Error('"COGNITIVE_SERVICES_SUBSCRIPTION_KEY" must be set.'); } else if (useDirectLineSpeech && !DIRECT_LINE_SPEECH_SUBSCRIPTION_KEY) { @@ -33,7 +33,7 @@ describe.each([ } const { token } = await ( - await fetch('https://webchat-mockbot3.azurewebsites.net/api/token/directline', { method: 'POST' }) + await fetch('https://hawo-mockbot4-token-app.ambitiousflower-67725bfd.westus.azurecontainerapps.io/api/token/directline', { method: 'POST' }) ).json(); const params = new URLSearchParams({ diff --git a/__tests__/html/video/vimeo.sandbox.html b/__tests__/html/video/vimeo.sandbox.html new file mode 100644 index 0000000000..73d2c59b06 --- /dev/null +++ b/__tests__/html/video/vimeo.sandbox.html @@ -0,0 +1,44 @@ + + + + + + + + + +
+ + + diff --git a/__tests__/html/video/vimeo.sandbox.js b/__tests__/html/video/vimeo.sandbox.js new file mode 100644 index 0000000000..b756788174 --- /dev/null +++ b/__tests__/html/video/vimeo.sandbox.js @@ -0,0 +1,3 @@ +/** @jest-environment ./packages/test/harness/src/host/jest/WebDriverEnvironment.js */ + +test('video/vimeo.sandbox.html', () => runHTML('video/vimeo.sandbox.html')); diff --git a/__tests__/html/video/youtube.sandbox.html b/__tests__/html/video/youtube.sandbox.html new file mode 100644 index 0000000000..8ed5f9b4d4 --- /dev/null +++ b/__tests__/html/video/youtube.sandbox.html @@ -0,0 +1,44 @@ + + + + + + + + + +
+ + + diff --git a/__tests__/html/video/youtube.sandbox.js b/__tests__/html/video/youtube.sandbox.js new file mode 100644 index 0000000000..5a6db95141 --- /dev/null +++ b/__tests__/html/video/youtube.sandbox.js @@ -0,0 +1,3 @@ +/** @jest-environment ./packages/test/harness/src/host/jest/WebDriverEnvironment.js */ + +test('video/youtube.sandbox.html', () => runHTML('video/youtube.sandbox.html')); diff --git a/__tests__/html/video/youtube.skip.html b/__tests__/html/video/youtube.skip.html new file mode 100644 index 0000000000..557e6f6ded --- /dev/null +++ b/__tests__/html/video/youtube.skip.html @@ -0,0 +1,80 @@ + + + + + + +
+ + + + diff --git a/__tests__/html/video/youtube.skip.js b/__tests__/html/video/youtube.skip.js new file mode 100644 index 0000000000..cfd0678d42 --- /dev/null +++ b/__tests__/html/video/youtube.skip.js @@ -0,0 +1,3 @@ +/** @jest-environment ./packages/test/harness/src/host/jest/WebDriverEnvironment.js */ + +test.skip('video/youtube.skip.html', () => runHTML('video/youtube.skip.html')); diff --git a/__tests__/sendBox.js b/__tests__/sendBox.js index 744c159b43..98b4b077fd 100644 --- a/__tests__/sendBox.js +++ b/__tests__/sendBox.js @@ -1,6 +1,5 @@ import { imageSnapshotOptions, timeouts } from './constants.json'; -import actionDispatched from './setup/conditions/actionDispatched'; import minNumActivitiesShown from './setup/conditions/minNumActivitiesShown'; import uiConnected from './setup/conditions/uiConnected'; @@ -32,23 +31,3 @@ test('should focus send box when message is being sent', async () => { expect(base64PNG).toMatchImageSnapshot(imageSnapshotOptions); }); - -test('should trim outgoing message when being sent', async () => { - const { driver, pageObjects } = await setupWebDriver(); - - await driver.wait(uiConnected(), timeouts.directLine); - await pageObjects.sendMessageViaSendBox( - '\u00A0\u00A0There should be no space before and after this message.\u00A0\u00A0', - { waitForSend: false } - ); - await driver.wait( - actionDispatched( - ({ payload: { activity } = {}, type }) => - type === 'DIRECT_LINE/INCOMING_ACTIVITY' && - activity.from.role === 'user' && - activity.text === 'There should be no space before and after this message.' - ), - timeouts.directLine - ); - await driver.wait(minNumActivitiesShown(2), timeouts.directLine); -}); diff --git a/__tests__/setup/web/index.html b/__tests__/setup/web/index.html index 23f5fb7ae4..cea691a8e6 100644 --- a/__tests__/setup/web/index.html +++ b/__tests__/setup/web/index.html @@ -1,4 +1,4 @@ - + Web Chat: Automated test harness @@ -167,10 +167,13 @@ } else { const { token } = await retry(async () => { try { - const res = await fetch('https://webchat-mockbot.azurewebsites.net/directline/token', { - method: 'POST', - timeout: 2000 - }); + const res = await fetch( + 'https://hawo-mockbot4-token-app.ambitiousflower-67725bfd.westus.azurecontainerapps.io/api/token/directline', + { + method: 'POST', + timeout: 2000 + } + ); return await res.json(); } catch (err) { diff --git a/__tests__/speech.synthesis.js b/__tests__/speech.synthesis.js deleted file mode 100644 index b1595f8b4d..0000000000 --- a/__tests__/speech.synthesis.js +++ /dev/null @@ -1,160 +0,0 @@ -import { timeouts } from './constants.json'; - -import minNumActivitiesShown from './setup/conditions/minNumActivitiesShown'; -import negationOf from './setup/conditions/negationOf'; -import speechRecognitionStartCalled from './setup/conditions/speechRecognitionStartCalled'; -import speechSynthesisUtterancePended from './setup/conditions/speechSynthesisUtterancePended'; - -// selenium-webdriver API doc: -// https://seleniumhq.github.io/selenium/docs/api/javascript/module/selenium-webdriver/index_exports_WebDriver.html - -jest.setTimeout(timeouts.test); - -describe('speech synthesis', () => { - // Verification of fix of #1736, https://github.com/microsoft/BotFramework-WebChat/issues/1736 - test('should synthesize two consecutive messages', async () => { - const { driver, pageObjects } = await setupWebDriver({ - props: { - webSpeechPonyfillFactory: () => window.WebSpeechMock - } - }); - - await pageObjects.sendMessageViaMicrophone('echo 123'); - - await driver.wait(minNumActivitiesShown(3), timeouts.directLine); - await driver.wait(speechSynthesisUtterancePended(), timeouts.ui); - - await expect(pageObjects.startSpeechSynthesize()).resolves.toHaveProperty( - 'text', - 'Echoing back in a separate activity.' - ); - - await pageObjects.endSpeechSynthesize(); - - await expect(pageObjects.startSpeechSynthesize()).resolves.toHaveProperty('text', '123'); - - await pageObjects.endSpeechSynthesize(); - }); - - // Verification of fix of #2096, https://github.com/microsoft/BotFramework-WebChat/issues/2096 - test('should synthesize speak property of Adaptive Card', async () => { - const { driver, pageObjects } = await setupWebDriver({ - props: { - webSpeechPonyfillFactory: () => window.WebSpeechMock - } - }); - - await pageObjects.sendMessageViaMicrophone('card bingsports'); - - await driver.wait(minNumActivitiesShown(2), timeouts.directLine); - await driver.wait(speechSynthesisUtterancePended(), timeouts.ui); - - await expect(pageObjects.startSpeechSynthesize()).resolves.toHaveProperty( - 'text', - 'Showing bingsports\r\nThe Seattle Seahawks beat the Carolina Panthers 40-7' - ); - - await pageObjects.endSpeechSynthesize(); - }); - - test('should start recognition after failing on speech synthesis with activity of expecting input', async () => { - const { driver, pageObjects } = await setupWebDriver({ - props: { - webSpeechPonyfillFactory: () => window.WebSpeechMock - } - }); - - await pageObjects.sendMessageViaMicrophone('hint expecting'); - - // TODO: [P3] #4046 Improves test reliability by identifying false positives and reduce wait time. - await expect(() => driver.wait(speechRecognitionStartCalled(), timeouts.ui)).rejects.toThrow( - 'Waiting SpeechRecognition.start to be called' - ); - await driver.wait(minNumActivitiesShown(2), timeouts.directLine); - await driver.wait(speechSynthesisUtterancePended(), timeouts.ui); - - await pageObjects.startSpeechSynthesize(); - await pageObjects.errorSpeechSynthesize(); - - await driver.wait(speechRecognitionStartCalled(), timeouts.ui); - }); - - test('should not synthesis if engine is explicitly configured off', async () => { - const { driver, pageObjects } = await setupWebDriver({ - props: { - webSpeechPonyfillFactory: () => { - const { SpeechGrammarList, SpeechRecognition } = window.WebSpeechMock; - - return { - SpeechGrammarList, - SpeechRecognition, - speechSynthesis: null, - SpeechSynthesisUtterance: null - }; - } - } - }); - - await pageObjects.sendMessageViaMicrophone('Hello, World!'); - - // TODO: [P3] #4046 Improves test reliability by identifying false positives and reduce wait time. - await expect(() => driver.wait(speechRecognitionStartCalled(), timeouts.ui)).rejects.toThrow( - 'Waiting SpeechRecognition.start to be called' - ); - await driver.wait(minNumActivitiesShown(2), timeouts.directLine); - - expect(await pageObjects.getConsoleErrors()).toEqual([]); - }); - - test('should stop synthesis after clicking on microphone button', async () => { - const { driver, pageObjects } = await setupWebDriver({ - props: { - webSpeechPonyfillFactory: () => window.WebSpeechMock - } - }); - - await pageObjects.sendMessageViaMicrophone('echo Hello, World!'); - - // TODO: [P3] #4046 Improves test reliability by identifying false positives and reduce wait time. - await expect(() => driver.wait(speechRecognitionStartCalled(), timeouts.ui)).rejects.toThrow( - 'Waiting SpeechRecognition.start to be called' - ); - await driver.wait(minNumActivitiesShown(3), timeouts.directLine); - - await expect(pageObjects.startSpeechSynthesize()).resolves.toHaveProperty( - 'text', - 'Echoing back in a separate activity.' - ); - - await driver.wait(speechSynthesisUtterancePended(), timeouts.ui); - - await pageObjects.clickMicrophoneButton(); - - await driver.wait(speechRecognitionStartCalled(), timeouts.ui); - await driver.wait(negationOf(speechSynthesisUtterancePended()), timeouts.ui); - }); - - describe('without speech synthesis', () => { - test('should start recognition immediately after receiving expected input hint', async () => { - const { driver, pageObjects } = await setupWebDriver({ - props: { - webSpeechPonyfillFactory: () => { - const { SpeechGrammarList, SpeechRecognition } = window.WebSpeechMock; - - return { - SpeechGrammarList, - SpeechRecognition - }; - } - } - }); - - await pageObjects.sendMessageViaMicrophone('hint expected'); - - await driver.wait(minNumActivitiesShown(2), timeouts.directLine); - - await driver.wait(speechRecognitionStartCalled(), timeouts.ui); - await driver.wait(negationOf(speechSynthesisUtterancePended()), timeouts.ui); - }); - }); -}); diff --git a/__tests__/video.js b/__tests__/video.js deleted file mode 100644 index 6250972ca5..0000000000 --- a/__tests__/video.js +++ /dev/null @@ -1,72 +0,0 @@ -import { By, until } from 'selenium-webdriver'; - -import { imageSnapshotOptions, timeouts } from './constants.json'; - -import allImagesLoaded from './setup/conditions/allImagesLoaded'; -import minNumActivitiesShown from './setup/conditions/minNumActivitiesShown.js'; - -// selenium-webdriver API doc: -// https://seleniumhq.github.io/selenium/docs/api/javascript/module/selenium-webdriver/index_exports_WebDriver.html - -jest.setTimeout(timeouts.test); - -async function clickButton(driver, locator) { - await driver.wait(until.elementLocated(locator), timeouts.ui); - - const button = await driver.findElement(locator); - - await button.click(); -} - -test('video', async () => { - const { driver, pageObjects } = await setupWebDriver(); - - await pageObjects.sendMessageViaSendBox('video youtube', { waitForSend: true }); - - await driver.wait(allImagesLoaded(), timeouts.fetchImage); - await driver.wait(minNumActivitiesShown(2), timeouts.directLine); - - await pageObjects.switchToYouTubeIFRAME(); - - // Play the video - await clickButton(driver, By.css('button[aria-label="Play"]')); - - // Wait until the video complete buffered and start playing - await driver.sleep(4000); - - // Pause the video - await clickButton(driver, By.css('button[data-title-no-tooltip="Pause"]')); - - // Jump back for 10 seconds, to get the buffering bar the same - await driver.actions().sendKeys('j').perform(); - - // Wait for controls to fade in - await driver.sleep(500); - - // Hide the spinner, play/pause/rewind and controls - await driver.executeScript(() => { - const spinner = document.querySelector('.ytp-spinner'); - - spinner && spinner.remove(); - - const bezelText = document.querySelector('.ytp-bezel-text-hide'); - - bezelText && bezelText.setAttribute('hidden', 'hidden'); - - const chromeBottom = document.querySelector('.ytp-chrome-bottom'); - - chromeBottom && chromeBottom.setAttribute('hidden', 'hidden'); - - const doubleTapUI = document.querySelector('.ytp-doubletap-ui-legacy'); - - doubleTapUI && doubleTapUI.setAttribute('hidden', 'hidden'); - - const tooltip = document.querySelector('.ytp-tooltip'); - - tooltip && tooltip.setAttribute('hidden', 'hidden'); - }); - - const base64PNG = await driver.takeScreenshot(); - - expect(base64PNG).toMatchImageSnapshot(imageSnapshotOptions); -}); diff --git a/package-lock.json b/package-lock.json index b76131b346..58d989ec8c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8211,6 +8211,20 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/call-bind-apply-helpers": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", + "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/callsites": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", @@ -9699,6 +9713,21 @@ "url": "https://dotenvx.com" } }, + "node_modules/dunder-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", + "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.1", + "es-errors": "^1.3.0", + "gopd": "^1.2.0" + }, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/duplexer": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/duplexer/-/duplexer-0.1.2.tgz", @@ -9922,13 +9951,11 @@ } }, "node_modules/es-define-property": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.0.tgz", - "integrity": "sha512-jxayLKShrEqqzJ0eumQbVhTYQM27CfT1T35+gCgDFoL82JLsXqTJ76zv6A0YLOgEnLUMvLzsDsGIrl8NFpT2gQ==", + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", + "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", "dev": true, - "dependencies": { - "get-intrinsic": "^1.2.4" - }, + "license": "MIT", "engines": { "node": ">= 0.4" } @@ -9994,10 +10021,11 @@ } }, "node_modules/es-object-atoms": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.0.0.tgz", - "integrity": "sha512-MZ4iQ6JwHOBQjahnjwaC1ZtIBH+2ohjamzAO3oaHcXYup7qxjF2fixyH+Q71voWHeOkI2q/TnJao/KfXYIZWbw==", + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", + "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", "dev": true, + "license": "MIT", "dependencies": { "es-errors": "^1.3.0" }, @@ -10006,14 +10034,16 @@ } }, "node_modules/es-set-tostringtag": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.0.3.tgz", - "integrity": "sha512-3T8uNMC3OQTHkFUsFq8r/BwAXLHvU/9O9mE0fBc/MY5iq/8H7ncvO947LmYA6ldWw9Uh8Yhf25zu6n7nML5QWQ==", + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz", + "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==", "dev": true, + "license": "MIT", "dependencies": { - "get-intrinsic": "^1.2.4", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.6", "has-tostringtag": "^1.0.2", - "hasown": "^2.0.1" + "hasown": "^2.0.2" }, "engines": { "node": ">= 0.4" @@ -11250,13 +11280,16 @@ } }, "node_modules/form-data": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz", - "integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==", + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.5.tgz", + "integrity": "sha512-8RipRLol37bNs2bhoV67fiTEvdTrbMUYcFTiy3+wuuOnUog2QBHCZWXDRijWQfAkhBj2Uf5UnVaiWwA5vdd82w==", "dev": true, + "license": "MIT", "dependencies": { "asynckit": "^0.4.0", "combined-stream": "^1.0.8", + "es-set-tostringtag": "^2.1.0", + "hasown": "^2.0.2", "mime-types": "^2.1.12" }, "engines": { @@ -11402,16 +11435,22 @@ } }, "node_modules/get-intrinsic": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.4.tgz", - "integrity": "sha512-5uYhsJH8VJBTv7oslg4BznJYhDoRI6waYCxMmCdnTrcCrHA/fCFKoTFz2JKKE0HdDFUF7/oQuhzumXJK7paBRQ==", + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", + "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", "dev": true, + "license": "MIT", "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "es-define-property": "^1.0.1", "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", "function-bind": "^1.1.2", - "has-proto": "^1.0.1", - "has-symbols": "^1.0.3", - "hasown": "^2.0.0" + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "math-intrinsics": "^1.1.0" }, "engines": { "node": ">= 0.4" @@ -11511,6 +11550,20 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/get-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", + "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", + "dev": true, + "license": "MIT", + "dependencies": { + "dunder-proto": "^1.0.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/get-stdin": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/get-stdin/-/get-stdin-5.0.1.tgz", @@ -11749,12 +11802,13 @@ "dev": true }, "node_modules/gopd": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.0.1.tgz", - "integrity": "sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==", + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", + "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", "dev": true, - "dependencies": { - "get-intrinsic": "^1.1.3" + "license": "MIT", + "engines": { + "node": ">= 0.4" }, "funding": { "url": "https://github.com/sponsors/ljharb" @@ -11779,13 +11833,14 @@ "dev": true }, "node_modules/handlebars": { - "version": "4.7.7", - "resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.7.7.tgz", - "integrity": "sha512-aAcXm5OAfE/8IXkcZvCepKU3VzW1/39Fb5ZuqMtgI/hT8X2YgoMvBY5dLhq/cpOvw7Lk1nK/UF71aLG/ZnVYRA==", + "version": "4.7.9", + "resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.7.9.tgz", + "integrity": "sha512-4E71E0rpOaQuJR2A3xDZ+GM1HyWYv1clR58tC8emQNeQe3RH7MAzSbat+V0wG78LQBo6m6bzSG/L4pBuCsgnUQ==", "dev": true, + "license": "MIT", "dependencies": { "minimist": "^1.2.5", - "neo-async": "^2.6.0", + "neo-async": "^2.6.2", "source-map": "^0.6.1", "wordwrap": "^1.0.0" }, @@ -11860,10 +11915,11 @@ } }, "node_modules/has-symbols": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz", - "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==", + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", + "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", "dev": true, + "license": "MIT", "engines": { "node": ">= 0.4" }, @@ -17222,6 +17278,16 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/math-intrinsics": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", + "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, "node_modules/meow": { "version": "8.1.2", "resolved": "https://registry.npmjs.org/meow/-/meow-8.1.2.tgz", @@ -30280,6 +30346,16 @@ "set-function-length": "^1.2.1" } }, + "call-bind-apply-helpers": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", + "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", + "dev": true, + "requires": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2" + } + }, "callsites": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", @@ -31384,6 +31460,17 @@ "integrity": "sha512-ZmdL2rui+eB2YwhsWzjInR8LldtZHGDoQ1ugH85ppHKwpUHL7j7rN0Ti9NCnGiQbhaZ11FpR+7ao1dNsmduNUg==", "dev": true }, + "dunder-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", + "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", + "dev": true, + "requires": { + "call-bind-apply-helpers": "^1.0.1", + "es-errors": "^1.3.0", + "gopd": "^1.2.0" + } + }, "duplexer": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/duplexer/-/duplexer-0.1.2.tgz", @@ -31573,13 +31660,10 @@ } }, "es-define-property": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.0.tgz", - "integrity": "sha512-jxayLKShrEqqzJ0eumQbVhTYQM27CfT1T35+gCgDFoL82JLsXqTJ76zv6A0YLOgEnLUMvLzsDsGIrl8NFpT2gQ==", - "dev": true, - "requires": { - "get-intrinsic": "^1.2.4" - } + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", + "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", + "dev": true }, "es-errors": { "version": "1.3.0", @@ -31635,23 +31719,24 @@ } }, "es-object-atoms": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.0.0.tgz", - "integrity": "sha512-MZ4iQ6JwHOBQjahnjwaC1ZtIBH+2ohjamzAO3oaHcXYup7qxjF2fixyH+Q71voWHeOkI2q/TnJao/KfXYIZWbw==", + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", + "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", "dev": true, "requires": { "es-errors": "^1.3.0" } }, "es-set-tostringtag": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.0.3.tgz", - "integrity": "sha512-3T8uNMC3OQTHkFUsFq8r/BwAXLHvU/9O9mE0fBc/MY5iq/8H7ncvO947LmYA6ldWw9Uh8Yhf25zu6n7nML5QWQ==", + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz", + "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==", "dev": true, "requires": { - "get-intrinsic": "^1.2.4", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.6", "has-tostringtag": "^1.0.2", - "hasown": "^2.0.1" + "hasown": "^2.0.2" } }, "es-shim-unscopables": { @@ -32565,13 +32650,15 @@ } }, "form-data": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz", - "integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==", + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.5.tgz", + "integrity": "sha512-8RipRLol37bNs2bhoV67fiTEvdTrbMUYcFTiy3+wuuOnUog2QBHCZWXDRijWQfAkhBj2Uf5UnVaiWwA5vdd82w==", "dev": true, "requires": { "asynckit": "^0.4.0", "combined-stream": "^1.0.8", + "es-set-tostringtag": "^2.1.0", + "hasown": "^2.0.2", "mime-types": "^2.1.12" } }, @@ -32674,16 +32761,21 @@ "dev": true }, "get-intrinsic": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.4.tgz", - "integrity": "sha512-5uYhsJH8VJBTv7oslg4BznJYhDoRI6waYCxMmCdnTrcCrHA/fCFKoTFz2JKKE0HdDFUF7/oQuhzumXJK7paBRQ==", + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", + "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", "dev": true, "requires": { + "call-bind-apply-helpers": "^1.0.2", + "es-define-property": "^1.0.1", "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", "function-bind": "^1.1.2", - "has-proto": "^1.0.1", - "has-symbols": "^1.0.3", - "hasown": "^2.0.0" + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "math-intrinsics": "^1.1.0" } }, "get-package-type": { @@ -32761,6 +32853,16 @@ "integrity": "sha512-g/Q1aTSDOxFpchXC4i8ZWvxA1lnPqx/JHqcpIw0/LX9T8x/GBbi6YnlN5nhaKIFkT8oFsscUKgDJYxfwfS6QsQ==", "dev": true }, + "get-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", + "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", + "dev": true, + "requires": { + "dunder-proto": "^1.0.1", + "es-object-atoms": "^1.0.0" + } + }, "get-stdin": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/get-stdin/-/get-stdin-5.0.1.tgz", @@ -32937,13 +33039,10 @@ "dev": true }, "gopd": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.0.1.tgz", - "integrity": "sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==", - "dev": true, - "requires": { - "get-intrinsic": "^1.1.3" - } + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", + "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", + "dev": true }, "graceful-fs": { "version": "4.2.10", @@ -32964,13 +33063,13 @@ "dev": true }, "handlebars": { - "version": "4.7.7", - "resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.7.7.tgz", - "integrity": "sha512-aAcXm5OAfE/8IXkcZvCepKU3VzW1/39Fb5ZuqMtgI/hT8X2YgoMvBY5dLhq/cpOvw7Lk1nK/UF71aLG/ZnVYRA==", + "version": "4.7.9", + "resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.7.9.tgz", + "integrity": "sha512-4E71E0rpOaQuJR2A3xDZ+GM1HyWYv1clR58tC8emQNeQe3RH7MAzSbat+V0wG78LQBo6m6bzSG/L4pBuCsgnUQ==", "dev": true, "requires": { "minimist": "^1.2.5", - "neo-async": "^2.6.0", + "neo-async": "^2.6.2", "source-map": "^0.6.1", "uglify-js": "^3.1.4", "wordwrap": "^1.0.0" @@ -33018,9 +33117,9 @@ "dev": true }, "has-symbols": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz", - "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==", + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", + "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", "dev": true }, "has-tostringtag": { @@ -36965,6 +37064,12 @@ } } }, + "math-intrinsics": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", + "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", + "dev": true + }, "meow": { "version": "8.1.2", "resolved": "https://registry.npmjs.org/meow/-/meow-8.1.2.tgz", diff --git a/packages/bundle/package-lock.json b/packages/bundle/package-lock.json index ab50215024..af0c370b4c 100644 --- a/packages/bundle/package-lock.json +++ b/packages/bundle/package-lock.json @@ -10,7 +10,7 @@ "license": "MIT", "dependencies": { "@babel/runtime": "7.19.0", - "adaptivecards": "3.0.2", + "adaptivecards": "3.0.6", "botframework-directlinejs": "0.15.5", "classnames": "2.5.1", "core-js": "3.37.0", @@ -24,7 +24,7 @@ "microsoft-cognitiveservices-speech-sdk": "1.17.0", "prop-types": "15.8.1", "sanitize-html": "2.13.0", - "swiper": "8.4.7", + "swiper": "12.1.4", "url-search-params-polyfill": "8.2.5", "uuid": "8.3.2", "web-speech-cognitive-services": "7.1.3", @@ -2715,11 +2715,12 @@ } }, "node_modules/adaptivecards": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/adaptivecards/-/adaptivecards-3.0.2.tgz", - "integrity": "sha512-ioniHtm8c5uENw6jmddnntpjC4MvSOjN2Xrg9YhxuTtoVqz0XoYrInRIq0uf9WmONm7p+wqjAqBkoI6IRDeNwA==", + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/adaptivecards/-/adaptivecards-3.0.6.tgz", + "integrity": "sha512-WDCIb2WXZ7cOHJ9HIHRNdhSt861HySVOdN2xPk2aKHAF3JvD8QGQ8/XcNFrDJxCteOog8MM7EBlXfSAR8JXKAg==", + "license": "MIT", "peerDependencies": { - "swiper": "^8.2.6" + "swiper": "^12.1.2" } }, "node_modules/agent-base": { @@ -3832,14 +3833,6 @@ "url": "https://github.com/cheeriojs/dom-serializer?sponsor=1" } }, - "node_modules/dom7": { - "version": "4.0.6", - "resolved": "https://registry.npmjs.org/dom7/-/dom7-4.0.6.tgz", - "integrity": "sha512-emjdpPLhpNubapLFdjNL9tP06Sr+GZkrIHEXLWvOGsytACUrkbeIdjO5g77m00BrHTznnlcNqgmn7pCN192TBA==", - "dependencies": { - "ssr-window": "^4.0.0" - } - }, "node_modules/domelementtype": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-2.3.0.tgz", @@ -7810,11 +7803,6 @@ "integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=", "dev": true }, - "node_modules/ssr-window": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/ssr-window/-/ssr-window-4.0.2.tgz", - "integrity": "sha512-ISv/Ch+ig7SOtw7G2+qkwfVASzazUnvlDTwypdLoPoySv+6MqlOV10VwPSE6EWkGjhW50lUmghPmpYZXMu/+AQ==" - }, "node_modules/string-width": { "version": "4.2.3", "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", @@ -7899,24 +7887,20 @@ } }, "node_modules/swiper": { - "version": "8.4.7", - "resolved": "https://registry.npmjs.org/swiper/-/swiper-8.4.7.tgz", - "integrity": "sha512-VwO/KU3i9IV2Sf+W2NqyzwWob4yX9Qdedq6vBtS0rFqJ6Fa5iLUJwxQkuD4I38w0WDJwmFl8ojkdcRFPHWD+2g==", + "version": "12.1.4", + "resolved": "https://registry.npmjs.org/swiper/-/swiper-12.1.4.tgz", + "integrity": "sha512-bihiwoKMOQwW8FfdUbo1DgkVH25E+4ZELIq0oopL1KTKBteLuaTMi/wwFjMxtlhTkk45k3XQ89D1Fvv0spSqBA==", "funding": [ { - "type": "patreon", - "url": "https://www.patreon.com/swiperjs" + "type": "custom", + "url": "https://sponsors.nolimits4web.com" }, { - "type": "open_collective", - "url": "http://opencollective.com/swiper" + "type": "github", + "url": "https://github.com/sponsors/nolimits4web" } ], - "hasInstallScript": true, - "dependencies": { - "dom7": "^4.0.4", - "ssr-window": "^4.0.2" - }, + "license": "MIT", "engines": { "node": ">= 4.7.0" } @@ -10460,9 +10444,9 @@ "dev": true }, "adaptivecards": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/adaptivecards/-/adaptivecards-3.0.2.tgz", - "integrity": "sha512-ioniHtm8c5uENw6jmddnntpjC4MvSOjN2Xrg9YhxuTtoVqz0XoYrInRIq0uf9WmONm7p+wqjAqBkoI6IRDeNwA==" + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/adaptivecards/-/adaptivecards-3.0.6.tgz", + "integrity": "sha512-WDCIb2WXZ7cOHJ9HIHRNdhSt861HySVOdN2xPk2aKHAF3JvD8QGQ8/XcNFrDJxCteOog8MM7EBlXfSAR8JXKAg==" }, "agent-base": { "version": "6.0.2", @@ -11258,14 +11242,6 @@ "entities": "^4.2.0" } }, - "dom7": { - "version": "4.0.6", - "resolved": "https://registry.npmjs.org/dom7/-/dom7-4.0.6.tgz", - "integrity": "sha512-emjdpPLhpNubapLFdjNL9tP06Sr+GZkrIHEXLWvOGsytACUrkbeIdjO5g77m00BrHTznnlcNqgmn7pCN192TBA==", - "requires": { - "ssr-window": "^4.0.0" - } - }, "domelementtype": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-2.3.0.tgz", @@ -13844,11 +13820,6 @@ "integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=", "dev": true }, - "ssr-window": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/ssr-window/-/ssr-window-4.0.2.tgz", - "integrity": "sha512-ISv/Ch+ig7SOtw7G2+qkwfVASzazUnvlDTwypdLoPoySv+6MqlOV10VwPSE6EWkGjhW50lUmghPmpYZXMu/+AQ==" - }, "string-width": { "version": "4.2.3", "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", @@ -13914,13 +13885,9 @@ } }, "swiper": { - "version": "8.4.7", - "resolved": "https://registry.npmjs.org/swiper/-/swiper-8.4.7.tgz", - "integrity": "sha512-VwO/KU3i9IV2Sf+W2NqyzwWob4yX9Qdedq6vBtS0rFqJ6Fa5iLUJwxQkuD4I38w0WDJwmFl8ojkdcRFPHWD+2g==", - "requires": { - "dom7": "^4.0.4", - "ssr-window": "^4.0.2" - } + "version": "12.1.4", + "resolved": "https://registry.npmjs.org/swiper/-/swiper-12.1.4.tgz", + "integrity": "sha512-bihiwoKMOQwW8FfdUbo1DgkVH25E+4ZELIq0oopL1KTKBteLuaTMi/wwFjMxtlhTkk45k3XQ89D1Fvv0spSqBA==" }, "symbol-observable": { "version": "1.0.1", diff --git a/packages/bundle/package.json b/packages/bundle/package.json index b5be9e29ac..09788b5cc2 100644 --- a/packages/bundle/package.json +++ b/packages/bundle/package.json @@ -126,7 +126,7 @@ }, "dependencies": { "@babel/runtime": "7.19.0", - "adaptivecards": "3.0.2", + "adaptivecards": "3.0.6", "botframework-directlinejs": "0.15.5", "botframework-directlinespeech-sdk": "0.0.0-0", "botframework-webchat-api": "0.0.0-0", @@ -144,7 +144,7 @@ "microsoft-cognitiveservices-speech-sdk": "1.17.0", "prop-types": "15.8.1", "sanitize-html": "2.13.0", - "swiper": "8.4.7", + "swiper": "12.1.4", "url-search-params-polyfill": "8.2.5", "uuid": "8.3.2", "web-speech-cognitive-services": "7.1.3", diff --git a/packages/test/harness/Dockerfile b/packages/test/harness/Dockerfile index d4a5763976..5c301d936a 100644 --- a/packages/test/harness/Dockerfile +++ b/packages/test/harness/Dockerfile @@ -1,13 +1,9 @@ # Setting to a different base image to secure your container supply chain. -ARG REGISTRY=docker.io -ARG BASE_IMAGE=$REGISTRY/node:18-alpine +ARG REGISTRY=mcr.microsoft.com +ARG BASE_IMAGE=$REGISTRY/devcontainers/javascript-node:24-bookworm FROM $BASE_IMAGE -RUN apk update && \ - apk upgrade && \ - apk add --no-cache bash git openssh - ENV PORT=80 EXPOSE 80 WORKDIR /var/jest-server/ diff --git a/packages/test/page-object/src/globals/testHelpers/token/fetchDirectLineAppServiceExtensionToken.js b/packages/test/page-object/src/globals/testHelpers/token/fetchDirectLineAppServiceExtensionToken.js index e85444420a..b1564731c8 100644 --- a/packages/test/page-object/src/globals/testHelpers/token/fetchDirectLineAppServiceExtensionToken.js +++ b/packages/test/page-object/src/globals/testHelpers/token/fetchDirectLineAppServiceExtensionToken.js @@ -1,5 +1,5 @@ export default async function fetchDirectLineAppServiceExtensionToken( - url = 'https://webchat-mockbot3.azurewebsites.net/api/token/directlinease' + url = 'https://hawo-mockbot4-token-app.ambitiousflower-67725bfd.westus.azurecontainerapps.io/api/token/directlinease' ) { const res = await fetch(url, { method: 'POST' }); @@ -7,7 +7,7 @@ export default async function fetchDirectLineAppServiceExtensionToken( throw new Error('Failed to fetch Direct Line App Service Extension token.'); } - const { token } = await res.json(); + const { domain, token } = await res.json(); - return token; + return { domain, token }; } diff --git a/packages/test/page-object/src/globals/testHelpers/token/fetchDirectLineToken.js b/packages/test/page-object/src/globals/testHelpers/token/fetchDirectLineToken.js index 092c05e1fd..dbe261599a 100644 --- a/packages/test/page-object/src/globals/testHelpers/token/fetchDirectLineToken.js +++ b/packages/test/page-object/src/globals/testHelpers/token/fetchDirectLineToken.js @@ -1,4 +1,6 @@ -export default async function fetchDirectLineToken(url = 'https://webchat-mockbot.azurewebsites.net/directline/token') { +export default async function fetchDirectLineToken( + url = 'https://hawo-mockbot4-token-app.ambitiousflower-67725bfd.westus.azurecontainerapps.io/api/token/directline' +) { const res = await fetch(url, { method: 'POST' }); if (!res.ok) { diff --git a/packages/test/page-object/src/globals/testHelpers/token/fetchSpeechServicesCredentials.js b/packages/test/page-object/src/globals/testHelpers/token/fetchSpeechServicesCredentials.js index cc32c57301..31fbd390cf 100644 --- a/packages/test/page-object/src/globals/testHelpers/token/fetchSpeechServicesCredentials.js +++ b/packages/test/page-object/src/globals/testHelpers/token/fetchSpeechServicesCredentials.js @@ -2,7 +2,9 @@ function createFetchSpeechServicesCredentials() { let expireAfter = 0; let resultPromise; - return (url = 'https://webchat-mockbot.azurewebsites.net/speechservices/token') => { + return ( + url = 'https://hawo-mockbot4-token-app.ambitiousflower-67725bfd.westus.azurecontainerapps.io/api/token/speech/msi' + ) => { if (!resultPromise || Date.now() > expireAfter) { expireAfter = Date.now() + 5000; resultPromise = fetch(url, { method: 'POST' }) diff --git a/playground.dockerfile b/playground.dockerfile index d9f747256c..d356b65432 100644 --- a/playground.dockerfile +++ b/playground.dockerfile @@ -1,13 +1,9 @@ # Setting to a different base image to secure your container supply chain. -ARG REGISTRY=docker.io -ARG BASE_IMAGE=$REGISTRY/node:18-alpine +ARG REGISTRY=mcr.microsoft.com +ARG BASE_IMAGE=$REGISTRY/devcontainers/javascript-node:24-bookworm FROM $BASE_IMAGE -RUN apk update && \ - apk upgrade && \ - apk add --no-cache bash git openssh - ENV PORT=80 EXPOSE 80 RUN npm install serve@10.0.0 -g diff --git a/serve-test.json b/serve-test.json index b20d7094fe..e10c4c12bd 100644 --- a/serve-test.json +++ b/serve-test.json @@ -15,6 +15,14 @@ "source": "/assets/:filename", "destination": "__tests__/html/assets/:filename" }, + { + "source": "/assets/:path/:filename", + "destination": "__tests__/html/assets/:path/:filename" + }, + { + "source": "/assets/:path1/:path2/:filename", + "destination": "__tests__/html/assets/:path1/:path2/:filename" + }, { "source": "/assets/transcripts/:filename", "destination": "__tests__/html/assets/transcripts/:filename" diff --git a/testharness.dockerfile b/testharness.dockerfile index 20d69017b0..c50b3c96ed 100644 --- a/testharness.dockerfile +++ b/testharness.dockerfile @@ -1,13 +1,9 @@ # Setting to a different base image to secure your container supply chain. -ARG REGISTRY=docker.io -ARG BASE_IMAGE=$REGISTRY/node:18-alpine +ARG REGISTRY=mcr.microsoft.com +ARG BASE_IMAGE=$REGISTRY/devcontainers/javascript-node:24-bookworm FROM $BASE_IMAGE -RUN apk update && \ - apk upgrade && \ - apk add --no-cache bash git openssh - ENV PORT=80 EXPOSE 80 RUN npm install serve@10.0.0 -g diff --git a/testharness2.dockerfile b/testharness2.dockerfile index e63f565817..0f53385a10 100644 --- a/testharness2.dockerfile +++ b/testharness2.dockerfile @@ -1,13 +1,9 @@ # Setting to a different base image to secure your container supply chain. -ARG REGISTRY=docker.io -ARG BASE_IMAGE=$REGISTRY/node:18-alpine +ARG REGISTRY=mcr.microsoft.com +ARG BASE_IMAGE=$REGISTRY/devcontainers/javascript-node:24-bookworm FROM $BASE_IMAGE -RUN apk update && \ - apk upgrade && \ - apk add --no-cache bash git openssh - ENV PORT=80 EXPOSE 80 RUN npm install serve@11.3.0 -g