diff --git a/.github/workflows/container-tefca-viewer.yaml b/.github/workflows/container-tefca-viewer.yaml index f9e9d02b..e3a82881 100644 --- a/.github/workflows/container-tefca-viewer.yaml +++ b/.github/workflows/container-tefca-viewer.yaml @@ -81,3 +81,8 @@ jobs: - name: Playwright Tests working-directory: ./containers/${{env.CONTAINER}} run: npx playwright test e2e --reporter=list --config playwright.config.ts + - uses: actions/upload-artifact@v4 + if: ${{ !cancelled() }} + with: + name: test-results + path: ./containers/${{env.CONTAINER}}/test-results/ diff --git a/containers/tefca-viewer/e2e/query_workflow.spec.ts b/containers/tefca-viewer/e2e/query_workflow.spec.ts index 1c740d0e..5fd47b08 100644 --- a/containers/tefca-viewer/e2e/query_workflow.spec.ts +++ b/containers/tefca-viewer/e2e/query_workflow.spec.ts @@ -1,11 +1,12 @@ // @ts-check import { test, expect } from "@playwright/test"; +import { TEST_URL } from "../playwright-setup"; test.describe("querying with the TryTEFCA viewer", () => { test.beforeEach(async ({ page }) => { // Start every test on our main landing page - await page.goto("http://localhost:3000/tefca-viewer"); + await page.goto(TEST_URL); }); test("landing page loads", async ({ page }) => { @@ -86,6 +87,7 @@ test.describe("querying with the TryTEFCA viewer", () => { await page.getByLabel("Medical Record Number").fill("18091"); await page.getByLabel("Phone Number").fill("5555555555"); await page.getByRole("button", { name: "Search for patient" }).click(); + await expect(page.getByText("Loading")).toHaveCount(0, { timeout: 10000 }); // Make sure we have a results page with a single patient // Non-interactive 'div' elements in the table should be located by text @@ -136,15 +138,16 @@ test.describe("querying with the TryTEFCA viewer", () => { // Among verification, make sure phone number is right await page.getByRole("button", { name: "Search for patient" }).click(); + await expect(page.getByText("Loading")).toHaveCount(0, { timeout: 10000 }); await expect( page.getByRole("heading", { name: "Patient Record" }), ).toBeVisible(); await expect(page.getByText("Patient Name")).toBeVisible(); - await expect(page.getByText("Veronica Anne Blackstone")).toBeVisible(); + await expect(page.getByText("Hyper A. Unlucky")).toBeVisible(); await expect(page.getByText("Contact")).toBeVisible(); - await expect(page.getByText("937-379-3497")).toBeVisible(); + await expect(page.getByText("517-425-1398")).toBeVisible(); await expect(page.getByText("Patient Identifiers")).toBeVisible(); - await expect(page.getByText("34972316")).toBeVisible(); + await expect(page.getByText("8692756")).toBeVisible(); }); test("social determinants query with generalized function", async ({ @@ -167,8 +170,9 @@ test.describe("querying with the TryTEFCA viewer", () => { await page.getByRole("button", { name: "Go to the demo" }).click(); await page.getByLabel("Query", { exact: true }).selectOption("chlamydia"); await page.getByRole("button", { name: "Fill fields" }).click(); - await page.getByLabel("Phone Number").fill(""); await page.getByRole("button", { name: "Search for patient" }).click(); + await expect(page.getByText("Loading")).toHaveCount(0, { timeout: 10000 }); + await expect( page.getByRole("heading", { name: "Patient Record" }), ).toBeVisible(); diff --git a/containers/tefca-viewer/package.json b/containers/tefca-viewer/package.json index cf9bc8fe..6beee41b 100644 --- a/containers/tefca-viewer/package.json +++ b/containers/tefca-viewer/package.json @@ -7,7 +7,7 @@ "dev": "docker compose -f docker-compose-dev.yaml up -d && next dev", "dev-win": "start docker compose -f docker-compose-dev.yaml up && next dev", "dev:db": "docker compose -f docker-compose-dev.yaml up", - "dev:next": "dotenv -e ./tefca.env.local -e ./tefca.env -- next dev", + "dev:next": "dotenv -e ./.env -- next dev", "setup-local-env": "./setup-env.sh", "build": "next build && cp -r .next/static .next/standalone/.next && cp -r public .next/standalone/", "start": "NODE_TLS_REJECT_UNAUTHORIZED=0 node .next/standalone/server.js", @@ -17,6 +17,7 @@ "test:unit:watch": "jest --watch", "test:integration": "jest --testPathPattern=tests/integration", "test:playwright": "docker compose build --no-cache && docker compose up -d && npx playwright test --reporter=list", + "test:playwright:local": "dotenv -e ./.env -- npx playwright test --ui", "cypress:open": "cypress open", "cypress:run": "cypress run" }, diff --git a/containers/tefca-viewer/playwright-setup.ts b/containers/tefca-viewer/playwright-setup.ts index a41940cf..1592a433 100644 --- a/containers/tefca-viewer/playwright-setup.ts +++ b/containers/tefca-viewer/playwright-setup.ts @@ -1,27 +1,35 @@ +/** + * + */ + +export const TEST_URL = + process.env.TEST_ENV ?? "http://localhost:3000/tefca-viewer"; + /** * */ async function globalSetup() { - const url = "http://localhost:3000/tefca-viewer"; const maxRetries = 300; // Maximum number of retries const delay = 1000; // Delay between retries in milliseconds for (let attempts = 0; attempts < maxRetries; attempts++) { try { - const response = await fetch(url); // Fetch the URL + const response = await fetch(TEST_URL); // Fetch the TEST_URL if (response.status === 200) { - console.log(`Connected to ${url} successfully.`); + console.log(`Connected to ${TEST_URL} successfully.`); return; // Exit the function if the webpage loads successfully } else { console.log( - `Failed to connect to ${url}, status: ${response.status}. Retrying...`, + `Failed to connect to ${TEST_URL}, status: ${response.status}. Retrying...`, ); // Wait before the next attempt await new Promise((resolve) => setTimeout(resolve, delay)); } } catch (error) { console.log( - `Fetch failed for ${url}: ${(error as Error).message}. Retrying...`, + `Fetch failed for ${TEST_URL}: ${ + (error as Error).message + }. Retrying...`, ); await new Promise((resolve) => setTimeout(resolve, delay)); } @@ -29,7 +37,9 @@ async function globalSetup() { await new Promise((resolve) => setTimeout(resolve, delay)); } - throw new Error(`Unable to connect to ${url} after ${maxRetries} attempts.`); + throw new Error( + `Unable to connect to ${TEST_URL} after ${maxRetries} attempts.`, + ); } export default globalSetup; diff --git a/containers/tefca-viewer/playwright.config.ts b/containers/tefca-viewer/playwright.config.ts index c2ab4b9b..697b4cd3 100644 --- a/containers/tefca-viewer/playwright.config.ts +++ b/containers/tefca-viewer/playwright.config.ts @@ -28,6 +28,7 @@ export default defineConfig({ /* Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer */ trace: "on-first-retry", + video: "on-first-retry", }, /* Configure projects for major browsers */ diff --git a/containers/tefca-viewer/src/app/constants.ts b/containers/tefca-viewer/src/app/constants.ts index 2534a772..d61ed241 100644 --- a/containers/tefca-viewer/src/app/constants.ts +++ b/containers/tefca-viewer/src/app/constants.ts @@ -61,7 +61,7 @@ export const FhirServers = [ "HELIOS Meld: eHealthExchange", "JMC Meld: Direct", "JMC Meld: eHealthExchange", - "Public HAPI: eHealthExchange", + "Public HAPI: Direct", "OpenEpic: eHealthExchange", "CernerHelios: eHealthExchange", "OPHDST Meld: Direct", @@ -90,37 +90,46 @@ export type PatientType = | "social-determinants" | "sti-syphilis-positive"; +/* + * Common "Hyper Unlucky" patient data used for all non-newborn screening use cases + */ +const hyperUnluckyPatient: DemoDataFields = { + FirstName: "Hyper", + LastName: "Unlucky", + DOB: "1975-12-06", + MRN: "8692756", + Phone: "517-425-1398", + FhirServer: "Public HAPI: Direct", + UseCase: "cancer", // UseCase will be updated per case +}; + /* Demo patient data used to populate the form fields with each value being a type of DemoDataFields */ export const demoData: Record = { - cancer: { - FirstName: "Lee", - LastName: "Shaw", - DOB: "1975-12-06", - MRN: "8692756", - Phone: "517-425-1398", - FhirServer: "HELIOS Meld: Direct", - UseCase: "cancer", - }, - "sti-chlamydia-positive": { - FirstName: "Chlamydia", - LastName: "JMC", - DOB: "2001-05-07", - MRN: "b50z-wayszq-ofib", - Phone: "", - FhirServer: "JMC Meld: Direct", - UseCase: "chlamydia", - }, - "sti-gonorrhea-positive": { - FirstName: "GC", - LastName: "JMC", - DOB: "1998-05-31", - MRN: "JMC-1002", - Phone: "", - FhirServer: "JMC Meld: Direct", - UseCase: "gonorrhea", + cancer: { ...hyperUnluckyPatient, UseCase: "cancer" }, + "sti-chlamydia-positive": { ...hyperUnluckyPatient, UseCase: "chlamydia" }, + "sti-gonorrhea-positive": { ...hyperUnluckyPatient, UseCase: "gonorrhea" }, + "social-determinants": { + ...hyperUnluckyPatient, + UseCase: "social-determinants", }, + "sti-syphilis-positive": { ...hyperUnluckyPatient, UseCase: "syphilis" }, + + // Newborn screening data remains unchanged + // We need to figure how to display specific cases for specific referral, fail, pass + // "newborn-screening-technical-fail": { + // ...hyperUnluckyPatient, + // UseCase: "newborn-screening", + // }, + // "newborn-screening-referral": { + // ...hyperUnluckyPatient, + // UseCase: "newborn-screening", + // }, + // "newborn-screening-pass": { + // ...hyperUnluckyPatient, + // UseCase: "newborn-screening", + // }, "newborn-screening-technical-fail": { FirstName: "Mango", LastName: "Smith", @@ -148,26 +157,9 @@ export const demoData: Record = { FhirServer: "CernerHelios: eHealthExchange", UseCase: "newborn-screening", }, - "social-determinants": { - FirstName: "Veronica", - LastName: "Blackstone", - DOB: "1998-06-18", - MRN: "34972316", - Phone: "937-379-3497", - FhirServer: "HELIOS Meld: Direct", - UseCase: "social-determinants", - }, - "sti-syphilis-positive": { - FirstName: "Veronica", - LastName: "Blackstone", - DOB: "1998-06-18", - MRN: "34972316", - Phone: "937-379-3497", - FhirServer: "HELIOS Meld: Direct", - UseCase: "syphilis", - }, }; +// Define Option type type Option = { value: string; label: string; diff --git a/containers/tefca-viewer/src/app/fhir-servers.ts b/containers/tefca-viewer/src/app/fhir-servers.ts index 998cca4a..98603b9b 100644 --- a/containers/tefca-viewer/src/app/fhir-servers.ts +++ b/containers/tefca-viewer/src/app/fhir-servers.ts @@ -14,20 +14,23 @@ type FHIR_SERVER_CONFIG = { */ export const fhirServers: Record = { "HELIOS Meld: Direct": { - hostname: "https://gw.interop.community/HeliosConnectathonSa/open/", + hostname: "https://gw.interop.community/HeliosConnectathonSa/open", init: {} as RequestInit, }, "HELIOS Meld: eHealthExchange": configureEHX("MeldOpen"), "JMC Meld: Direct": { - hostname: "https://gw.interop.community/JMCHeliosSTISandbox/open/", + hostname: "https://gw.interop.community/JMCHeliosSTISandbox/open", init: {} as RequestInit, }, "JMC Meld: eHealthExchange": configureEHX("JMCHelios"), - "Public HAPI: eHealthExchange": configureEHX("PublicHAPI"), + "Public HAPI: Direct": { + hostname: "https://hapi.fhir.org/baseR4", + init: {} as RequestInit, + }, "OpenEpic: eHealthExchange": configureEHX("OpenEpic"), "CernerHelios: eHealthExchange": configureEHX("CernerHelios"), "OPHDST Meld: Direct": { - hostname: "https://gw.interop.community/CDCSepHL7Connectatho/open/", + hostname: "https://gw.interop.community/CDCSepHL7Connectatho/open", init: {} as RequestInit, }, }; diff --git a/containers/tefca-viewer/src/app/query-service.ts b/containers/tefca-viewer/src/app/query-service.ts index 65d4fce4..6b2030c9 100644 --- a/containers/tefca-viewer/src/app/query-service.ts +++ b/containers/tefca-viewer/src/app/query-service.ts @@ -98,7 +98,7 @@ async function patientQuery( queryResponse: QueryResponse, ): Promise { // Query for patient - let query = "Patient?"; + let query = "/Patient?"; if (request.first_name) { query += `given=${request.first_name}&`; } diff --git a/containers/tefca-viewer/src/app/query/components/searchForm/SearchForm.tsx b/containers/tefca-viewer/src/app/query/components/searchForm/SearchForm.tsx index ef733131..aedac810 100644 --- a/containers/tefca-viewer/src/app/query/components/searchForm/SearchForm.tsx +++ b/containers/tefca-viewer/src/app/query/components/searchForm/SearchForm.tsx @@ -67,6 +67,7 @@ const SearchForm: React.FC = ({ const [firstName, setFirstName] = useState(""); const [lastName, setLastName] = useState(""); const [fhirServer, setFhirServer] = useState(); + // "Public HAPI: Direct", const [phone, setPhone] = useState(""); const [dob, setDOB] = useState(""); const [mrn, setMRN] = useState(""); @@ -248,6 +249,7 @@ const SearchForm: React.FC = ({ name="fhir_server" value={fhirServer} defaultValue={""} + // defaultValue={"Public HAPI: Direct"} onChange={(event) => { setFhirServer(event.target.value as FHIR_SERVERS); }}