From cc89597693deb26453f28484293448a59b4cce59 Mon Sep 17 00:00:00 2001 From: Morten Barklund Date: Thu, 9 Jul 2026 12:05:05 +0200 Subject: [PATCH 1/5] test: validate embedded component communication --- .github/workflows/release.yml | 45 +++-- README.md | 42 +++++ test/corti-embedded.integration.test.ts | 214 ++++++++++++++++++++++++ web-test-runner.config.js | 151 ++++++++++++++++- 4 files changed, 433 insertions(+), 19 deletions(-) create mode 100644 test/corti-embedded.integration.test.ts diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 6528c84..603438c 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -5,7 +5,7 @@ on: branches: - main tags: - - 'v*' + - "v*" pull_request: branches: - main @@ -20,8 +20,8 @@ jobs: - name: Setup Node.js uses: actions/setup-node@v4 with: - node-version: 'lts/*' - cache: 'npm' + node-version: "lts/*" + cache: "npm" - name: Install dependencies run: npm ci @@ -38,8 +38,8 @@ jobs: - name: Setup Node.js uses: actions/setup-node@v4 with: - node-version: '24.15.0' - cache: 'npm' + node-version: "24.15.0" + cache: "npm" - name: Install dependencies run: npm ci @@ -60,6 +60,29 @@ jobs: if-no-files-found: ignore retention-days: 1 + edge-integration: + runs-on: windows-latest + steps: + - name: Checkout repo + uses: actions/checkout@v4 + + - name: Setup Node.js + uses: actions/setup-node@v4 + with: + node-version: "24.15.0" + cache: "npm" + + - name: Install dependencies + run: npm ci + + - name: Run Edge integration test + env: + EMBEDDED_WEB_TEST_BROWSERS: chromium + EMBEDDED_WEB_CHROMIUM_CHANNEL: msedge + run: | + npx tsc -p tsconfig.test.json + npx wtr .tmp/test-dist/test/corti-embedded.integration.test.js --coverage=false + build: runs-on: ubuntu-latest steps: @@ -69,8 +92,8 @@ jobs: - name: Setup Node.js uses: actions/setup-node@v4 with: - node-version: 'lts/*' - cache: 'npm' + node-version: "lts/*" + cache: "npm" - name: Install dependencies run: npm ci @@ -79,7 +102,7 @@ jobs: run: npm run prerelease publish: - needs: [typecheck, test, build] + needs: [typecheck, test, edge-integration, build] if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags/v') runs-on: ubuntu-latest permissions: @@ -92,9 +115,9 @@ jobs: - name: Setup Node.js uses: actions/setup-node@v4 with: - node-version: 'lts/*' - cache: 'npm' - registry-url: 'https://registry.npmjs.org' + node-version: "lts/*" + cache: "npm" + registry-url: "https://registry.npmjs.org" - name: Install dependencies run: npm ci diff --git a/README.md b/README.md index 6902576..ec8c564 100644 --- a/README.md +++ b/README.md @@ -15,6 +15,48 @@ A web component and React component library that provides an embedded interface npm install @corti/embedded-web ``` +## Testing + +Install dependencies before running tests: + +```bash +npm install +``` + +Run the full test suite: + +```bash +npm test +``` + +Run tests in watch mode while developing: + +```bash +npm run test:watch +``` + +Run only the embedded component communication integration test: + +```bash +npx tsc -p tsconfig.test.json && npx wtr .tmp/test-dist/test/corti-embedded.integration.test.js --coverage=false +``` + +Run the integration test across Chromium, Firefox, and WebKit: + +```bash +npx tsc -p tsconfig.test.json && EMBEDDED_WEB_TEST_BROWSERS=chromium,firefox,webkit npx wtr .tmp/test-dist/test/corti-embedded.integration.test.js --coverage=false +``` + +Run the integration test against an installed Microsoft Edge channel: + +```bash +npx tsc -p tsconfig.test.json && EMBEDDED_WEB_TEST_BROWSERS=chromium EMBEDDED_WEB_CHROMIUM_CHANNEL=msedge npx wtr .tmp/test-dist/test/corti-embedded.integration.test.js --coverage=false +``` + +The integration test runs in Playwright Chromium by default and can be expanded to Firefox, WebKit, or an installed Edge channel. It validates the public web component API against a real iframe `postMessage` boundary and uses the local test runner setup, so it does not require access to the live embedded Assistant service. + +Pull requests also run the integration test against Microsoft Edge on a Windows GitHub Actions runner. + ## Usage ### Web Component diff --git a/test/corti-embedded.integration.test.ts b/test/corti-embedded.integration.test.ts new file mode 100644 index 0000000..f30a76e --- /dev/null +++ b/test/corti-embedded.integration.test.ts @@ -0,0 +1,214 @@ +import { expect, fixture } from "@open-wc/testing"; +import { html } from "lit"; +import type { CortiEmbedded } from "../src/CortiEmbedded.js"; +import "../src/corti-embedded.js"; +import type { + ConfigureApplicationPayload, + CreateInteractionPayload, + KeycloakTokenResponse, +} from "../src/web-index.js"; + +interface EmbeddedEventDetail { + name: string; + payload: unknown; +} + +interface RequestReceivedPayload { + action: string; + payload?: unknown; + hasRequestId: boolean; + version: string; +} + +const integrationBaseURL = "https://assistant.integration.corti.app"; + +const authPayload: KeycloakTokenResponse = { + access_token: "integration-token", + token_type: "Bearer", +}; + +const createInteractionPayload: CreateInteractionPayload = { + assignedUserId: "integration-user", + encounter: { + identifier: "integration-encounter", + status: "planned", + type: "first_consultation", + period: { + startedAt: "2026-07-09T00:00:00.000Z", + }, + title: "Integration Encounter", + }, + patient: { + identifier: "integration-patient", + name: "Integration Patient", + }, +}; + +const configureAppPayload: ConfigureApplicationPayload = { + debug: true, + ui: { navigation: true }, + appearance: { primaryColor: "#0055ff" }, +}; + +function waitForEmbeddedEvent( + el: CortiEmbedded, + eventName: string, +): Promise> { + return new Promise(resolve => { + const listener = (event: Event) => { + const customEvent = event as CustomEvent; + if (customEvent.detail?.name !== eventName) { + return; + } + + el.removeEventListener("event", listener); + resolve(customEvent); + }; + + el.addEventListener("event", listener); + }); +} + +async function waitForEmbeddedReady(el: CortiEmbedded): Promise { + return new Promise((resolve, reject) => { + let timeoutId: ReturnType; + + const intervalId = setInterval(() => { + if (!el.getDebugStatus().postMessageHandlerReady) { + return; + } + + clearInterval(intervalId); + clearTimeout(timeoutId); + resolve(); + }, 10); + + timeoutId = setTimeout(() => { + clearInterval(intervalId); + reject(new Error("Timed out waiting for the embedded integration frame")); + }, 2000); + }); +} + +async function mountEmbeddedIntegration(): Promise { + const el = await fixture( + html``, + ); + + await waitForEmbeddedReady(el); + return el; +} + +describe("CortiEmbedded browser integration", () => { + it("round-trips public API calls through a real iframe", async () => { + const el = await mountEmbeddedIntegration(); + const requestEvents: RequestReceivedPayload[] = []; + + const collectRequestEvent = async (eventPromise: Promise) => { + const event = await eventPromise; + requestEvents.push(event.detail.payload as RequestReceivedPayload); + }; + + await Promise.all([ + collectRequestEvent(waitForEmbeddedEvent(el, "test.request-received")), + el.auth(authPayload).then(user => { + expect(user).to.deep.equal({ + id: "integration-user", + email: "integration@example.test", + }); + }), + ]); + + await Promise.all([ + collectRequestEvent(waitForEmbeddedEvent(el, "test.request-received")), + el.createInteraction(createInteractionPayload).then(interaction => { + expect(interaction).to.deep.equal({ + id: "integration-interaction", + createdAt: "2026-07-09T00:00:00.000Z", + }); + }), + ]); + + await Promise.all([ + collectRequestEvent(waitForEmbeddedEvent(el, "test.request-received")), + el.configureApp(configureAppPayload).then(config => { + expect(config.ui.navigation).to.equal(true); + }), + ]); + + await Promise.all([ + collectRequestEvent(waitForEmbeddedEvent(el, "test.request-received")), + el.getStatus().then(status => { + expect(status.auth.isAuthenticated).to.equal(true); + expect(status.currentUrl).to.equal("/summary"); + }), + ]); + + await Promise.all([ + collectRequestEvent(waitForEmbeddedEvent(el, "test.request-received")), + el.getTemplates().then(result => { + expect(result.templates[0].id).to.equal("integration-template"); + }), + ]); + + expect( + requestEvents.map(({ action, hasRequestId, version }) => ({ + action, + hasRequestId, + version, + })), + ).to.deep.equal([ + { + action: "auth", + hasRequestId: true, + version: "v1", + }, + { + action: "createInteraction", + hasRequestId: true, + version: "v1", + }, + { + action: "configureApp", + hasRequestId: true, + version: "v1", + }, + { + action: "getStatus", + hasRequestId: true, + version: "v1", + }, + { + action: "getTemplates", + hasRequestId: true, + version: "v1", + }, + ]); + expect(requestEvents[0].payload).to.deep.include(authPayload); + expect(requestEvents[1].payload).to.deep.equal(createInteractionPayload); + expect(requestEvents[2].payload).to.deep.equal(configureAppPayload); + expect(requestEvents[3].payload).to.deep.equal({}); + }); + + it("surfaces events emitted by the embedded iframe", async () => { + const el = await mountEmbeddedIntegration(); + const requestReceived = waitForEmbeddedEvent(el, "test.request-received"); + const navigated = waitForEmbeddedEvent(el, "embedded.navigated"); + + await el.navigate({ path: "/summary" }); + + const requestEvent = await requestReceived; + expect(requestEvent.detail.payload).to.deep.equal({ + action: "navigate", + payload: { path: "/summary" }, + hasRequestId: true, + version: "v1", + }); + + const navigatedEvent = await navigated; + expect(navigatedEvent.detail).to.deep.equal({ + name: "embedded.navigated", + payload: { path: "/summary" }, + }); + }); +}); diff --git a/web-test-runner.config.js b/web-test-runner.config.js index 0ff45d6..f9429ef 100644 --- a/web-test-runner.config.js +++ b/web-test-runner.config.js @@ -1,22 +1,157 @@ -import { playwrightLauncher } from '@web/test-runner-playwright'; +import { playwrightLauncher } from "@web/test-runner-playwright"; -const filteredLogs = ['Running in dev mode', 'Lit is in dev mode']; +const filteredLogs = ["Running in dev mode", "Lit is in dev mode"]; + +const embeddedIntegrationBaseURL = "https://assistant.integration.corti.app"; + +const browserProducts = (process.env.EMBEDDED_WEB_TEST_BROWSERS || "chromium") + .split(",") + .map(browser => browser.trim()) + .filter(Boolean); + +const chromiumChannel = process.env.EMBEDDED_WEB_CHROMIUM_CHANNEL; + +const embeddedIntegrationFrameHtml = ` + + + + Corti Embedded Integration Frame + + + + +`; + +function createBrowserLauncher(product) { + return playwrightLauncher({ + product, + launchOptions: + product === "chromium" && chromiumChannel + ? { channel: chromiumChannel } + : {}, + createBrowserContext: async ({ browser }) => { + const context = await browser.newContext(); + + await context.route(`${embeddedIntegrationBaseURL}/embedded`, route => + route.fulfill({ + status: 200, + contentType: "text/html", + body: embeddedIntegrationFrameHtml, + }), + ); + + return context; + }, + }); +} export default /** @type {import("@web/test-runner").TestRunnerConfig} */ ({ /** Test files to run */ - files: '.tmp/test-dist/test/**/*.test.js', + files: ".tmp/test-dist/test/**/*.test.js", /** Resolve bare module imports */ nodeResolve: { - exportConditions: ['browser', 'development'], + exportConditions: ["browser", "development"], }, /** Use Playwright Chromium (bundled) so no CHROME_PATH is required */ - browsers: [playwrightLauncher({ product: 'chromium' })], + browsers: browserProducts.map(createBrowserLauncher), coverageConfig: { - include: ['src/**/*.ts'], - exclude: ['.tmp/test-dist/test/vendor/**'], + include: ["src/**/*.ts"], + exclude: [".tmp/test-dist/test/vendor/**"], }, testRunnerHtml: testFrameworkImport => ` @@ -36,7 +171,7 @@ export default /** @type {import("@web/test-runner").TestRunnerConfig} */ ({ /** Filter out lit dev mode logs */ filterBrowserLogs(log) { for (const arg of log.args) { - if (typeof arg === 'string' && filteredLogs.some(l => arg.includes(l))) { + if (typeof arg === "string" && filteredLogs.some(l => arg.includes(l))) { return false; } } From 92976cc9e77aa5d443af13225185e72f397dd0f7 Mon Sep 17 00:00:00 2001 From: Morten Barklund Date: Thu, 9 Jul 2026 12:17:23 +0200 Subject: [PATCH 2/5] test: harden embedded integration harness --- test/corti-embedded.integration.test.ts | 34 ++++++++----------------- web-test-runner.config.js | 14 +++++++++- 2 files changed, 24 insertions(+), 24 deletions(-) diff --git a/test/corti-embedded.integration.test.ts b/test/corti-embedded.integration.test.ts index f30a76e..3f7c53d 100644 --- a/test/corti-embedded.integration.test.ts +++ b/test/corti-embedded.integration.test.ts @@ -53,40 +53,28 @@ const configureAppPayload: ConfigureApplicationPayload = { function waitForEmbeddedEvent( el: CortiEmbedded, eventName: string, + timeoutMs = 2000, ): Promise> { - return new Promise(resolve => { + return new Promise((resolve, reject) => { + let timeoutId: ReturnType; + const listener = (event: Event) => { const customEvent = event as CustomEvent; if (customEvent.detail?.name !== eventName) { return; } + clearTimeout(timeoutId); el.removeEventListener("event", listener); resolve(customEvent); }; - el.addEventListener("event", listener); - }); -} - -async function waitForEmbeddedReady(el: CortiEmbedded): Promise { - return new Promise((resolve, reject) => { - let timeoutId: ReturnType; - - const intervalId = setInterval(() => { - if (!el.getDebugStatus().postMessageHandlerReady) { - return; - } - - clearInterval(intervalId); - clearTimeout(timeoutId); - resolve(); - }, 10); - timeoutId = setTimeout(() => { - clearInterval(intervalId); - reject(new Error("Timed out waiting for the embedded integration frame")); - }, 2000); + el.removeEventListener("event", listener); + reject(new Error(`Timed out waiting for ${eventName}`)); + }, timeoutMs); + + el.addEventListener("event", listener); }); } @@ -95,7 +83,7 @@ async function mountEmbeddedIntegration(): Promise { html``, ); - await waitForEmbeddedReady(el); + await waitForEmbeddedEvent(el, "embedded.ready"); return el; } diff --git a/web-test-runner.config.js b/web-test-runner.config.js index f9429ef..d1781de 100644 --- a/web-test-runner.config.js +++ b/web-test-runner.config.js @@ -75,8 +75,20 @@ const embeddedIntegrationFrameHtml = ` const readyInterval = window.setInterval(postReady, 25); window.addEventListener('message', event => { + const request = event.data; + if ( + event.source !== parent || + !request || + typeof request !== 'object' || + request.type !== 'CORTI_EMBEDDED' || + request.version !== 'v1' || + typeof request.action !== 'string' || + typeof request.requestId !== 'string' + ) { + return; + } + window.clearInterval(readyInterval); - const request = event.data || {}; postToParent({ type: 'CORTI_EMBEDDED_EVENT', From 473ab44e25c16e71a2466a7ee81770756f31cd35 Mon Sep 17 00:00:00 2001 From: Morten Barklund Date: Thu, 9 Jul 2026 13:00:43 +0200 Subject: [PATCH 3/5] test: cover embedded public api integration --- .github/workflows/release.yml | 19 +- README.md | 1 + test/corti-embedded.integration.test.ts | 224 +++++++++++++++++++----- web-test-runner.config.js | 15 ++ 4 files changed, 211 insertions(+), 48 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 603438c..b2ba560 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -51,14 +51,14 @@ jobs: - name: Test run: npm run test - - name: Upload coverage artifact + - name: Upload browser test report if: always() uses: actions/upload-artifact@v4 with: - name: coverage-report + name: browser-test-report path: coverage - if-no-files-found: ignore - retention-days: 1 + if-no-files-found: warn + retention-days: 7 edge-integration: runs-on: windows-latest @@ -81,7 +81,16 @@ jobs: EMBEDDED_WEB_CHROMIUM_CHANNEL: msedge run: | npx tsc -p tsconfig.test.json - npx wtr .tmp/test-dist/test/corti-embedded.integration.test.js --coverage=false + npx wtr .tmp/test-dist/test/corti-embedded.integration.test.js + + - name: Upload Edge integration report + if: always() + uses: actions/upload-artifact@v4 + with: + name: edge-integration-report + path: coverage + if-no-files-found: warn + retention-days: 7 build: runs-on: ubuntu-latest diff --git a/README.md b/README.md index ec8c564..4b20abe 100644 --- a/README.md +++ b/README.md @@ -56,6 +56,7 @@ npx tsc -p tsconfig.test.json && EMBEDDED_WEB_TEST_BROWSERS=chromium EMBEDDED_WE The integration test runs in Playwright Chromium by default and can be expanded to Firefox, WebKit, or an installed Edge channel. It validates the public web component API against a real iframe `postMessage` boundary and uses the local test runner setup, so it does not require access to the live embedded Assistant service. Pull requests also run the integration test against Microsoft Edge on a Windows GitHub Actions runner. +The workflow uploads generated reports as the `browser-test-report` and `edge-integration-report` artifacts. ## Usage diff --git a/test/corti-embedded.integration.test.ts b/test/corti-embedded.integration.test.ts index 3f7c53d..3241709 100644 --- a/test/corti-embedded.integration.test.ts +++ b/test/corti-embedded.integration.test.ts @@ -3,9 +3,15 @@ import { html } from "lit"; import type { CortiEmbedded } from "../src/CortiEmbedded.js"; import "../src/corti-embedded.js"; import type { + ConfigurePayload, ConfigureApplicationPayload, CreateInteractionPayload, + Fact, KeycloakTokenResponse, + NavigatePayload, + SessionConfig, + SetCredentialsPayload, + SetInteractionOptionsPayload, } from "../src/web-index.js"; interface EmbeddedEventDetail { @@ -50,6 +56,42 @@ const configureAppPayload: ConfigureApplicationPayload = { appearance: { primaryColor: "#0055ff" }, }; +const configurePayload: ConfigurePayload = { + debug: false, + features: { aiChat: false }, + appearance: { primaryColor: null }, +}; + +const sessionConfig: SessionConfig = { + defaultLanguage: "en", + defaultMode: "virtual", +}; + +const factsPayload: Fact[] = [ + { + text: "Patient reports chest pain", + group: "subjective", + }, +]; + +const interactionOptionsPayload: SetInteractionOptionsPayload = { + mode: { + fallback: "virtual", + options: ["virtual"], + }, + spokenLanguage: { + fallback: "en", + }, +}; + +const credentialsPayload: SetCredentialsPayload = { + password: "integration-password", +}; + +const navigatePayload: NavigatePayload = { + path: "/summary", +}; + function waitForEmbeddedEvent( el: CortiEmbedded, eventName: string, @@ -87,57 +129,91 @@ async function mountEmbeddedIntegration(): Promise { return el; } +async function captureRequest( + el: CortiEmbedded, + requestEvents: RequestReceivedPayload[], + callback: () => Promise, +): Promise { + const requestEvent = waitForEmbeddedEvent(el, "test.request-received"); + const [event, result] = await Promise.all([requestEvent, callback()]); + requestEvents.push(event.detail.payload as RequestReceivedPayload); + return result; +} + +async function withoutConsoleWarn(callback: () => Promise): Promise { + const originalWarn = console.warn; + console.warn = () => {}; + + try { + return await callback(); + } finally { + console.warn = originalWarn; + } +} + describe("CortiEmbedded browser integration", () => { it("round-trips public API calls through a real iframe", async () => { const el = await mountEmbeddedIntegration(); const requestEvents: RequestReceivedPayload[] = []; - const collectRequestEvent = async (eventPromise: Promise) => { - const event = await eventPromise; - requestEvents.push(event.detail.payload as RequestReceivedPayload); - }; + const user = await captureRequest(el, requestEvents, () => + el.auth(authPayload), + ); + expect(user).to.deep.equal({ + id: "integration-user", + email: "integration@example.test", + }); - await Promise.all([ - collectRequestEvent(waitForEmbeddedEvent(el, "test.request-received")), - el.auth(authPayload).then(user => { - expect(user).to.deep.equal({ - id: "integration-user", - email: "integration@example.test", - }); - }), - ]); + const interaction = await captureRequest(el, requestEvents, () => + el.createInteraction(createInteractionPayload), + ); + expect(interaction).to.deep.equal({ + id: "integration-interaction", + createdAt: "2026-07-09T00:00:00.000Z", + }); - await Promise.all([ - collectRequestEvent(waitForEmbeddedEvent(el, "test.request-received")), - el.createInteraction(createInteractionPayload).then(interaction => { - expect(interaction).to.deep.equal({ - id: "integration-interaction", - createdAt: "2026-07-09T00:00:00.000Z", - }); - }), - ]); + await withoutConsoleWarn(() => + captureRequest(el, requestEvents, () => + el.configureSession(sessionConfig), + ), + ); - await Promise.all([ - collectRequestEvent(waitForEmbeddedEvent(el, "test.request-received")), - el.configureApp(configureAppPayload).then(config => { - expect(config.ui.navigation).to.equal(true); - }), - ]); + await captureRequest(el, requestEvents, () => el.addFacts(factsPayload)); - await Promise.all([ - collectRequestEvent(waitForEmbeddedEvent(el, "test.request-received")), - el.getStatus().then(status => { - expect(status.auth.isAuthenticated).to.equal(true); - expect(status.currentUrl).to.equal("/summary"); - }), - ]); + await captureRequest(el, requestEvents, () => el.navigate(navigatePayload)); - await Promise.all([ - collectRequestEvent(waitForEmbeddedEvent(el, "test.request-received")), - el.getTemplates().then(result => { - expect(result.templates[0].id).to.equal("integration-template"); - }), - ]); + await captureRequest(el, requestEvents, () => el.startRecording()); + + await captureRequest(el, requestEvents, () => el.stopRecording()); + + const configureAppResponse = await captureRequest(el, requestEvents, () => + el.configureApp(configureAppPayload), + ); + expect(configureAppResponse.ui.navigation).to.equal(true); + + const configureResponse = await withoutConsoleWarn(() => + captureRequest(el, requestEvents, () => el.configure(configurePayload)), + ); + expect(configureResponse.features.aiChat).to.equal(false); + + await captureRequest(el, requestEvents, () => + el.setInteractionOptions(interactionOptionsPayload), + ); + + await captureRequest(el, requestEvents, () => + el.setCredentials(credentialsPayload), + ); + + const status = await captureRequest(el, requestEvents, () => + el.getStatus(), + ); + expect(status.auth.isAuthenticated).to.equal(true); + expect(status.currentUrl).to.equal("/summary"); + + const templates = await captureRequest(el, requestEvents, () => + el.getTemplates(), + ); + expect(templates.templates[0].id).to.equal("integration-template"); expect( requestEvents.map(({ action, hasRequestId, version }) => ({ @@ -156,11 +232,51 @@ describe("CortiEmbedded browser integration", () => { hasRequestId: true, version: "v1", }, + { + action: "configureSession", + hasRequestId: true, + version: "v1", + }, + { + action: "addFacts", + hasRequestId: true, + version: "v1", + }, + { + action: "navigate", + hasRequestId: true, + version: "v1", + }, + { + action: "startRecording", + hasRequestId: true, + version: "v1", + }, + { + action: "stopRecording", + hasRequestId: true, + version: "v1", + }, { action: "configureApp", hasRequestId: true, version: "v1", }, + { + action: "configure", + hasRequestId: true, + version: "v1", + }, + { + action: "setInteractionOptions", + hasRequestId: true, + version: "v1", + }, + { + action: "setCredentials", + hasRequestId: true, + version: "v1", + }, { action: "getStatus", hasRequestId: true, @@ -174,8 +290,16 @@ describe("CortiEmbedded browser integration", () => { ]); expect(requestEvents[0].payload).to.deep.include(authPayload); expect(requestEvents[1].payload).to.deep.equal(createInteractionPayload); - expect(requestEvents[2].payload).to.deep.equal(configureAppPayload); - expect(requestEvents[3].payload).to.deep.equal({}); + expect(requestEvents[2].payload).to.deep.include(sessionConfig); + expect(requestEvents[3].payload).to.deep.equal({ facts: factsPayload }); + expect(requestEvents[4].payload).to.deep.equal(navigatePayload); + expect(requestEvents[5].payload).to.deep.equal({}); + expect(requestEvents[6].payload).to.deep.equal({}); + expect(requestEvents[7].payload).to.deep.equal(configureAppPayload); + expect(requestEvents[8].payload).to.deep.equal(configurePayload); + expect(requestEvents[9].payload).to.deep.equal(interactionOptionsPayload); + expect(requestEvents[10].payload).to.deep.equal(credentialsPayload); + expect(requestEvents[11].payload).to.deep.equal({}); }); it("surfaces events emitted by the embedded iframe", async () => { @@ -199,4 +323,18 @@ describe("CortiEmbedded browser integration", () => { payload: { path: "/summary" }, }); }); + + it("toggles visibility through local public methods", async () => { + const el = await mountEmbeddedIntegration(); + const iframe = el.shadowRoot!.querySelector("iframe") as HTMLIFrameElement; + + expect(iframe.getAttribute("style")).to.contain("display: none"); + el.show(); + await el.updateComplete; + expect(iframe.getAttribute("style")).to.contain("display: block"); + + el.hide(); + await el.updateComplete; + expect(iframe.getAttribute("style")).to.contain("display: none"); + }); }); diff --git a/web-test-runner.config.js b/web-test-runner.config.js index d1781de..aacf06c 100644 --- a/web-test-runner.config.js +++ b/web-test-runner.config.js @@ -37,6 +37,21 @@ const embeddedIntegrationFrameHtml = ` locale: { interfaceLanguage: 'en' }, network: { websocketBaseUrl: null } }, + configure: { + debug: false, + appearance: { primaryColor: null }, + features: { + interactionTitle: true, + aiChat: false, + documentFeedback: true, + navigation: true, + virtualMode: false, + syncDocumentAction: true, + templateEditor: false + }, + locale: { interfaceLanguage: 'en' }, + network: { websocketBaseUrl: null } + }, getStatus: { auth: { isAuthenticated: true, From c1e4a24b4bcdfff2c0c5fe975932b67acefe4afb Mon Sep 17 00:00:00 2001 From: Morten Barklund Date: Thu, 9 Jul 2026 13:03:35 +0200 Subject: [PATCH 4/5] ci: generate Edge integration report --- .github/workflows/release.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index b2ba560..b617a76 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -81,7 +81,7 @@ jobs: EMBEDDED_WEB_CHROMIUM_CHANNEL: msedge run: | npx tsc -p tsconfig.test.json - npx wtr .tmp/test-dist/test/corti-embedded.integration.test.js + npx wtr .tmp/test-dist/test/corti-embedded.integration.test.js --coverage - name: Upload Edge integration report if: always() From 85616c3bb6df3850c703e7c21ca6105f22f11418 Mon Sep 17 00:00:00 2001 From: Morten Barklund Date: Thu, 9 Jul 2026 13:10:18 +0200 Subject: [PATCH 5/5] ci: disable empty WTR coverage reports --- .github/workflows/release.yml | 25 +++++-------------------- README.md | 7 +++---- 2 files changed, 8 insertions(+), 24 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index b617a76..d8ad8a4 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -49,16 +49,10 @@ jobs: timeout-minutes: 5 - name: Test - run: npm run test - - - name: Upload browser test report - if: always() - uses: actions/upload-artifact@v4 - with: - name: browser-test-report - path: coverage - if-no-files-found: warn - retention-days: 7 + run: | + npx tsc -p tsconfig.test.json + npm run build:test-react-bundle + npx wtr edge-integration: runs-on: windows-latest @@ -81,16 +75,7 @@ jobs: EMBEDDED_WEB_CHROMIUM_CHANNEL: msedge run: | npx tsc -p tsconfig.test.json - npx wtr .tmp/test-dist/test/corti-embedded.integration.test.js --coverage - - - name: Upload Edge integration report - if: always() - uses: actions/upload-artifact@v4 - with: - name: edge-integration-report - path: coverage - if-no-files-found: warn - retention-days: 7 + npx wtr .tmp/test-dist/test/corti-embedded.integration.test.js build: runs-on: ubuntu-latest diff --git a/README.md b/README.md index 4b20abe..4f8c9ec 100644 --- a/README.md +++ b/README.md @@ -38,25 +38,24 @@ npm run test:watch Run only the embedded component communication integration test: ```bash -npx tsc -p tsconfig.test.json && npx wtr .tmp/test-dist/test/corti-embedded.integration.test.js --coverage=false +npx tsc -p tsconfig.test.json && npx wtr .tmp/test-dist/test/corti-embedded.integration.test.js ``` Run the integration test across Chromium, Firefox, and WebKit: ```bash -npx tsc -p tsconfig.test.json && EMBEDDED_WEB_TEST_BROWSERS=chromium,firefox,webkit npx wtr .tmp/test-dist/test/corti-embedded.integration.test.js --coverage=false +npx tsc -p tsconfig.test.json && EMBEDDED_WEB_TEST_BROWSERS=chromium,firefox,webkit npx wtr .tmp/test-dist/test/corti-embedded.integration.test.js ``` Run the integration test against an installed Microsoft Edge channel: ```bash -npx tsc -p tsconfig.test.json && EMBEDDED_WEB_TEST_BROWSERS=chromium EMBEDDED_WEB_CHROMIUM_CHANNEL=msedge npx wtr .tmp/test-dist/test/corti-embedded.integration.test.js --coverage=false +npx tsc -p tsconfig.test.json && EMBEDDED_WEB_TEST_BROWSERS=chromium EMBEDDED_WEB_CHROMIUM_CHANNEL=msedge npx wtr .tmp/test-dist/test/corti-embedded.integration.test.js ``` The integration test runs in Playwright Chromium by default and can be expanded to Firefox, WebKit, or an installed Edge channel. It validates the public web component API against a real iframe `postMessage` boundary and uses the local test runner setup, so it does not require access to the live embedded Assistant service. Pull requests also run the integration test against Microsoft Edge on a Windows GitHub Actions runner. -The workflow uploads generated reports as the `browser-test-report` and `edge-integration-report` artifacts. ## Usage