diff --git a/.gitignore b/.gitignore index 5ef6a52..546c174 100644 --- a/.gitignore +++ b/.gitignore @@ -39,3 +39,11 @@ yarn-error.log* # typescript *.tsbuildinfo next-env.d.ts + +# Playwright +node_modules/ +/test-results/ +/playwright-report/ +/blob-report/ +/playwright/.cache/ +/playwright/.auth/ diff --git a/package-lock.json b/package-lock.json index 66b7d03..4c26709 100644 --- a/package-lock.json +++ b/package-lock.json @@ -47,6 +47,7 @@ }, "devDependencies": { "@eslint/eslintrc": "^3", + "@playwright/test": "^1.63.0", "@tailwindcss/postcss": "^4", "@types/node": "^20", "@types/pg": "^8.11.11", @@ -2261,6 +2262,22 @@ "node": ">=12.4.0" } }, + "node_modules/@playwright/test": { + "version": "1.63.0", + "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.63.0.tgz", + "integrity": "sha512-oxMK4vllB9RK5NQ2l1pq1IfOf2AvnEuj/vYGDj0H2nMtmtZpKtCwt/l00GEO6xjGfpBNAvjovvYdCm50dRQkpQ==", + "devOptional": true, + "license": "Apache-2.0", + "dependencies": { + "playwright": "1.63.0" + }, + "bin": { + "playwright": "cli.js" + }, + "engines": { + "node": ">=20" + } + }, "node_modules/@popperjs/core": { "version": "2.11.8", "resolved": "https://registry.npmjs.org/@popperjs/core/-/core-2.11.8.tgz", @@ -8196,6 +8213,35 @@ "url": "https://github.com/sponsors/jonschlinkert" } }, + "node_modules/playwright": { + "version": "1.63.0", + "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.63.0.tgz", + "integrity": "sha512-+7ziBLidS4NaNCdt57SUDT+wYmmd5fmiQejUic/kb+YsYSCPyOOE9sebzMjNmQrsnNpDJqd4WHvV/8lfKfUDUg==", + "devOptional": true, + "license": "Apache-2.0", + "dependencies": { + "playwright-core": "1.63.0" + }, + "bin": { + "playwright": "cli.js" + }, + "engines": { + "node": ">=20" + } + }, + "node_modules/playwright-core": { + "version": "1.63.0", + "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.63.0.tgz", + "integrity": "sha512-rYCsBF/M5HjUch52bbtVONEFjv6Xu8sm8h72dNlR5bzIE1fvC/bxgspzkjSfU+MweEMmPM8KJebG6nnyxo5mCg==", + "devOptional": true, + "license": "Apache-2.0", + "bin": { + "playwright-core": "cli.js" + }, + "engines": { + "node": ">=20" + } + }, "node_modules/possible-typed-array-names": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/possible-typed-array-names/-/possible-typed-array-names-1.1.0.tgz", diff --git a/package.json b/package.json index 9ff3f6f..1afdf2f 100644 --- a/package.json +++ b/package.json @@ -7,6 +7,7 @@ "build": "next build", "start": "next start", "lint": "next lint", + "test:e2e": "playwright test", "preview": "next build && next start" }, "dependencies": { @@ -49,6 +50,7 @@ }, "devDependencies": { "@eslint/eslintrc": "^3", + "@playwright/test": "^1.63.0", "@tailwindcss/postcss": "^4", "@types/node": "^20", "@types/pg": "^8.11.11", diff --git a/playwright.config.ts b/playwright.config.ts new file mode 100644 index 0000000..ae75537 --- /dev/null +++ b/playwright.config.ts @@ -0,0 +1,79 @@ +import {defineConfig, devices} from '@playwright/test'; + +/** + * See https://playwright.dev/docs/test-configuration. + */ +export default defineConfig({ + testDir: './tests', + /* Run tests in files in parallel */ + fullyParallel: true, + /* Fail the build on CI if you accidentally left test.only in the source code. */ + forbidOnly: !!process.env.CI, + /* Retry on CI only */ + retries: process.env.CI ? 2 : 0, + /* Opt out of parallel tests on CI. */ + workers: process.env.CI ? 1 : undefined, + /* Reporter to use. See https://playwright.dev/docs/test-reporters */ + reporter: 'html', + /* Shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions. */ + use: { + /* Base URL to use in actions like `await page.goto('')`. */ + baseURL: 'http://localhost:3000', + /* Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer */ + trace: 'on-first-retry', + screenshot: 'only-on-failure', + }, + expect: { + toHaveScreenshot: { + pathTemplate: '{testDir}/__screenshots__{/projectName}/{testFilePath}/{arg}{ext}', + }, + toMatchAriaSnapshot: { + pathTemplate: '{testDir}/__snapshots__/{testFilePath}/{arg}{ext}', + } + }, + + /* Configure projects for major browsers */ + projects: [ + { + name: 'chromium', + use: {...devices['Desktop Chrome']}, + }, + + { + name: 'firefox', + use: {...devices['Desktop Firefox']}, + }, + + { + name: 'webkit', + use: {...devices['Desktop Safari']}, + }, + + /* Test against mobile viewports. */ + // { + // name: 'Mobile Chrome', + // use: { ...devices['Pixel 5'] }, + // }, + // { + // name: 'Mobile Safari', + // use: { ...devices['iPhone 12'] }, + // }, + + /* Test against branded browsers. */ + // { + // name: 'Microsoft Edge', + // use: { ...devices['Desktop Edge'], channel: 'msedge' }, + // }, + // { + // name: 'Google Chrome', + // use: { ...devices['Desktop Chrome'], channel: 'chrome' }, + // }, + ], + + /* Run your local dev server before starting the tests */ + webServer: { + command: 'npm run preview', + url: 'http://localhost:3000', + reuseExistingServer: !process.env.CI, + }, +}); diff --git a/src/app/layout.tsx b/src/app/layout.tsx index a4ac2e1..ee7798c 100644 --- a/src/app/layout.tsx +++ b/src/app/layout.tsx @@ -36,7 +36,7 @@ export default function RootLayout({ className={`${geistSans.variable} ${geistMono.variable} antialiased`} > - + {/* */} diff --git a/src/components/app-sidebar.tsx b/src/components/app-sidebar.tsx index 3fdf088..32953c4 100644 --- a/src/components/app-sidebar.tsx +++ b/src/components/app-sidebar.tsx @@ -164,7 +164,7 @@ export function AppSidebar({ ...props }: React.ComponentProps) { } return ( - + diff --git a/tests/__snapshots__/homepage.spec.ts/landing-page-breadcrumbs-match-snapshot-1.aria.yml b/tests/__snapshots__/homepage.spec.ts/landing-page-breadcrumbs-match-snapshot-1.aria.yml new file mode 100644 index 0000000..dc9c2dc --- /dev/null +++ b/tests/__snapshots__/homepage.spec.ts/landing-page-breadcrumbs-match-snapshot-1.aria.yml @@ -0,0 +1,7 @@ +- button "Toggle Sidebar": + - img + - text: "" +- navigation "breadcrumb": + - list: + - listitem: + - link "Home" [disabled] \ No newline at end of file diff --git a/tests/__snapshots__/homepage.spec.ts/landing-page-main-content-matches-snapshot-1.aria.yml b/tests/__snapshots__/homepage.spec.ts/landing-page-main-content-matches-snapshot-1.aria.yml new file mode 100644 index 0000000..ecbd1e1 --- /dev/null +++ b/tests/__snapshots__/homepage.spec.ts/landing-page-main-content-matches-snapshot-1.aria.yml @@ -0,0 +1,28 @@ +- main: + - button "Toggle Sidebar": + - img + - text: "" + - navigation "breadcrumb": + - list: + - listitem: + - link "Home" [disabled] + - heading "๐Ÿ‘‹ Welcome to AV QC Portal" [level=2] + - paragraph: + - text: This web portal is actively being developed as a companion to the + - link "AV QC pipeline": + - /url: https://github.com/dptools/dpinterview + - text: project, designed to streamline audiovisual quality control processes. + - link "๐Ÿ” Quality Control Monitor and manage quality issues in your audio/video recordings with more intuitive interfaces.": + - /url: /issues + - heading "๐Ÿ” Quality Control" [level=4] + - paragraph: Monitor and manage quality issues in your audio/video recordings with more intuitive interfaces. + - link "๐Ÿ“Š Real-time Monitoring Track performance metrics and health indicators of your audio/video processing systems in real time. [Superset]": + - /url: http://localhost:8088 + - heading "๐Ÿ“Š Real-time Monitoring" [level=4] + - paragraph: Track performance metrics and health indicators of your audio/video processing systems in real time. [Superset] + - paragraph: + - text: This project is under active development. Check back for updates or contribute on + - link "GitHub": + - /url: https://github.com/dheshanm/dpinterview-web + - text: . + - region "Notifications alt+T" \ No newline at end of file diff --git a/tests/__snapshots__/homepage.spec.ts/landing-page-sidebar-matches-snapshot-1.aria.yml b/tests/__snapshots__/homepage.spec.ts/landing-page-sidebar-matches-snapshot-1.aria.yml new file mode 100644 index 0000000..9491bcb --- /dev/null +++ b/tests/__snapshots__/homepage.spec.ts/landing-page-sidebar-matches-snapshot-1.aria.yml @@ -0,0 +1,42 @@ +- navigation "sidebar": + - list: + - listitem: + - link "AV QC Portal v0.2.0": + - /url: / + - img + - text: "" + - text: Navigation + - list: + - listitem: + - link "Issues": + - /url: /issues + - img + - text: "" + - button "Toggle": + - img + - text: "" + - listitem: + - link "Interviews": + - /url: /interviews + - img + - text: "" + - button "Toggle": + - img + - text: "" + - listitem: + - link "Audio Journals": + - /url: /journals + - img + - text: "" + - list: + - listitem: + - link "GitHub": + - /url: https://github.com/dheshanm/dpinterview-web + - img + - text: "" + - listitem: + - link "Superset": + - /url: http://localhost:8088 + - img + - text: "" + - button "Toggle Sidebar" \ No newline at end of file diff --git a/tests/__snapshots__/homepage.spec.ts/landing-page-test-1.aria.yml b/tests/__snapshots__/homepage.spec.ts/landing-page-test-1.aria.yml new file mode 100644 index 0000000..ecbd1e1 --- /dev/null +++ b/tests/__snapshots__/homepage.spec.ts/landing-page-test-1.aria.yml @@ -0,0 +1,28 @@ +- main: + - button "Toggle Sidebar": + - img + - text: "" + - navigation "breadcrumb": + - list: + - listitem: + - link "Home" [disabled] + - heading "๐Ÿ‘‹ Welcome to AV QC Portal" [level=2] + - paragraph: + - text: This web portal is actively being developed as a companion to the + - link "AV QC pipeline": + - /url: https://github.com/dptools/dpinterview + - text: project, designed to streamline audiovisual quality control processes. + - link "๐Ÿ” Quality Control Monitor and manage quality issues in your audio/video recordings with more intuitive interfaces.": + - /url: /issues + - heading "๐Ÿ” Quality Control" [level=4] + - paragraph: Monitor and manage quality issues in your audio/video recordings with more intuitive interfaces. + - link "๐Ÿ“Š Real-time Monitoring Track performance metrics and health indicators of your audio/video processing systems in real time. [Superset]": + - /url: http://localhost:8088 + - heading "๐Ÿ“Š Real-time Monitoring" [level=4] + - paragraph: Track performance metrics and health indicators of your audio/video processing systems in real time. [Superset] + - paragraph: + - text: This project is under active development. Check back for updates or contribute on + - link "GitHub": + - /url: https://github.com/dheshanm/dpinterview-web + - text: . + - region "Notifications alt+T" \ No newline at end of file diff --git a/tests/__snapshots__/interviews.spec.ts/Interviews-page-breadcrumbs-match-snapshot-1.aria.yml b/tests/__snapshots__/interviews.spec.ts/Interviews-page-breadcrumbs-match-snapshot-1.aria.yml new file mode 100644 index 0000000..b264ae1 --- /dev/null +++ b/tests/__snapshots__/interviews.spec.ts/Interviews-page-breadcrumbs-match-snapshot-1.aria.yml @@ -0,0 +1,10 @@ +- button "Toggle Sidebar": + - img + - text: "" +- navigation "breadcrumb": + - list: + - listitem: + - link "Home": + - /url: / + - listitem: + - link "Interviews" [disabled] \ No newline at end of file diff --git a/tests/__snapshots__/interviews.spec.ts/Interviews-page-sidebar-matches-snapshot-1.aria.yml b/tests/__snapshots__/interviews.spec.ts/Interviews-page-sidebar-matches-snapshot-1.aria.yml new file mode 100644 index 0000000..ecbe11b --- /dev/null +++ b/tests/__snapshots__/interviews.spec.ts/Interviews-page-sidebar-matches-snapshot-1.aria.yml @@ -0,0 +1,49 @@ +- navigation "sidebar": + - list: + - listitem: + - link "AV QC Portal v0.2.0": + - /url: / + - img + - text: "" + - text: Navigation + - list: + - listitem: + - link "Issues": + - /url: /issues + - img + - text: "" + - button "Toggle": + - img + - text: "" + - listitem: + - link "Interviews": + - /url: /interviews + - img + - text: "" + - button "Toggle" [expanded]: + - img + - text: "" + - list: + - listitem: + - link "Pending QC": + - /url: /interviews/qc/pending + - listitem: + - link "Completed QC": + - /url: /interviews/qc/completed + - listitem: + - link "Audio Journals": + - /url: /journals + - img + - text: "" + - list: + - listitem: + - link "GitHub": + - /url: https://github.com/dheshanm/dpinterview-web + - img + - text: "" + - listitem: + - link "Superset": + - /url: http://localhost:8088 + - img + - text: "" + - button "Toggle Sidebar" \ No newline at end of file diff --git a/tests/__snapshots__/issues/multiPart.spec.ts/landing-page-sidebar-1.aria.yml b/tests/__snapshots__/issues/multiPart.spec.ts/landing-page-sidebar-1.aria.yml new file mode 100644 index 0000000..5629a2a --- /dev/null +++ b/tests/__snapshots__/issues/multiPart.spec.ts/landing-page-sidebar-1.aria.yml @@ -0,0 +1,82 @@ +- navigation "sidebar": + - list: + - listitem: + - link "AV QC Portal v0.2.0": + - /url: / + - img + - text: "" + - text: Navigation + - list: + - listitem: + - link "Issues": + - /url: /issues + - img + - text: "" + - button "Toggle" [expanded]: + - img + - text: "" + - list: + - listitem: + - link "Multi-Part Interviews": + - /url: /issues/multiPart + - listitem: + - link "Multi-Combined Audio Files": + - /url: /issues/multiCombinedAudio + - listitem: + - link "Unlabelled Audio": + - /url: /issues/unlabelledAudio + - listitem: + - link "Missing Interviews": + - /url: /issues/missing + - listitem: + - link "Missing Runsheets": + - /url: /issues/noRunsheet + - listitem: + - link "Missing Transcripts": + - /url: /issues/noTranscript + - listitem: + - link "Failed Audio QC": + - /url: /issues/audioQcFailed + - listitem: + - link "Pending Transcription Push": + - /url: /issues/pendingTranscriptionPush + - listitem: + - link "Awaiting Vendor Transcription": + - /url: /issues/awaitingVendorTranscription + - listitem: + - link "Transcript Not Imported": + - /url: /issues/transcriptNotImported + - listitem: + - link "Pipeline Failures": + - /url: /issues/pipelineFailures + - listitem: + - link "Runsheet Match": + - /url: /issues/runsheetMatch + - listitem: + - link "Override Ledger": + - /url: /issues/overrideLedger + - listitem: + - link "Interviews": + - /url: /interviews + - img + - text: "" + - button "Toggle": + - img + - text: "" + - listitem: + - link "Audio Journals": + - /url: /journals + - img + - text: "" + - list: + - listitem: + - link "GitHub": + - /url: https://github.com/dheshanm/dpinterview-web + - img + - text: "" + - listitem: + - link "Superset": + - /url: http://localhost:8088 + - img + - text: "" + - button "Toggle Sidebar" \ No newline at end of file diff --git a/tests/__snapshots__/journals.spec.ts/Audio-journals-page-breadcrumbs-match-snapshot-1.aria.yml b/tests/__snapshots__/journals.spec.ts/Audio-journals-page-breadcrumbs-match-snapshot-1.aria.yml new file mode 100644 index 0000000..f228e76 --- /dev/null +++ b/tests/__snapshots__/journals.spec.ts/Audio-journals-page-breadcrumbs-match-snapshot-1.aria.yml @@ -0,0 +1,10 @@ +- button "Toggle Sidebar": + - img + - text: "" +- navigation "breadcrumb": + - list: + - listitem: + - link "Home": + - /url: / + - listitem: + - link "Audio Journals" [disabled] \ No newline at end of file diff --git a/tests/__snapshots__/journals.spec.ts/Audio-journals-page-sidebar-matches-snapshot-1.aria.yml b/tests/__snapshots__/journals.spec.ts/Audio-journals-page-sidebar-matches-snapshot-1.aria.yml new file mode 100644 index 0000000..9491bcb --- /dev/null +++ b/tests/__snapshots__/journals.spec.ts/Audio-journals-page-sidebar-matches-snapshot-1.aria.yml @@ -0,0 +1,42 @@ +- navigation "sidebar": + - list: + - listitem: + - link "AV QC Portal v0.2.0": + - /url: / + - img + - text: "" + - text: Navigation + - list: + - listitem: + - link "Issues": + - /url: /issues + - img + - text: "" + - button "Toggle": + - img + - text: "" + - listitem: + - link "Interviews": + - /url: /interviews + - img + - text: "" + - button "Toggle": + - img + - text: "" + - listitem: + - link "Audio Journals": + - /url: /journals + - img + - text: "" + - list: + - listitem: + - link "GitHub": + - /url: https://github.com/dheshanm/dpinterview-web + - img + - text: "" + - listitem: + - link "Superset": + - /url: http://localhost:8088 + - img + - text: "" + - button "Toggle Sidebar" \ No newline at end of file diff --git a/tests/homepage.spec.ts b/tests/homepage.spec.ts new file mode 100644 index 0000000..b1ed1f0 --- /dev/null +++ b/tests/homepage.spec.ts @@ -0,0 +1,18 @@ +import {expect} from '@playwright/test'; +import {test} from "@tests/playwright"; + +test.describe("landing page", async () => { + + test("breadcrumbs match snapshot", async ({home}) => { + await expect(home.breadcrumbs).toMatchAriaSnapshot(); + }); + + test("sidebar matches snapshot", async ({home}) => { + await expect(home.sidebar).toMatchAriaSnapshot(); + }); + + test('main content matches snapshot', async ({home}) => { + await expect(home.main).toMatchAriaSnapshot(); + }); + +}); \ No newline at end of file diff --git a/tests/interviews.spec.ts b/tests/interviews.spec.ts new file mode 100644 index 0000000..2181a25 --- /dev/null +++ b/tests/interviews.spec.ts @@ -0,0 +1,52 @@ +import {expect} from '@playwright/test'; +import {test} from "@tests/playwright"; + +test.describe("Interviews page", async () => { + + test("breadcrumbs match snapshot", async ({interviews}) => { + await expect(interviews.breadcrumbs).toMatchAriaSnapshot(); + }); + + test("sidebar matches snapshot", async ({interviews}) => { + await expect(interviews.sidebar).toMatchAriaSnapshot(); + }); + + test('alert text is present', async ({interviews}) => { + await expect(interviews.main).toMatchAriaSnapshot(` + - alert: + - paragraph: Please use Superset Dashboard to look at the Aggregated Interview data. + `); + }); + + test.describe("interviews table", async () => { + + test('info text is present', async ({interviews}) => { + await expect(interviews.main).toMatchAriaSnapshot(`- paragraph: /The following \\d+ interviews have been identified for processing\\./`); + }); + + test('controls are present', async ({interviews}) => { + await expect(interviews.selectColumnsButton).toBeVisible(); + await expect(interviews.filtersButton).toBeVisible(); + await expect(interviews.densityButton).toBeVisible(); + await expect(interviews.exportButton).toBeVisible(); + }); + + test('columns are present', async ({interviews}) => { + await expect(interviews.selectRowHeader).toBeVisible(); + await expect(interviews.nameColumn).toBeVisible(); + await expect(interviews.typeColumn).toBeVisible(); + await expect(interviews.subjectColumn).toBeVisible(); + await expect(interviews.studyColumn).toBeVisible(); + }); + + test('table footer is present', async ({interviews}) => { + await expect(interviews.main).toMatchAriaSnapshot(` + - paragraph: "Rows per page:" + - 'combobox /Rows per page: \\d+/' + - paragraph: /1โ€“\\d+ of \\d+/ + - button "Go to previous page" [disabled] + - button "Go to next page" + `); + }); + }); +}); \ No newline at end of file diff --git a/tests/issues.spec.ts b/tests/issues.spec.ts new file mode 100644 index 0000000..8f9253e --- /dev/null +++ b/tests/issues.spec.ts @@ -0,0 +1,151 @@ +import {expect, test} from '@playwright/test'; + +test.describe("issues page", async () => { + test.beforeEach(async ({page}) => { + await page.goto("/issues"); + }); + + test('test main content', async ({page}) => { + await expect(page.getByRole('main')).toMatchAriaSnapshot(` + - heading "Issues" [level=2] + - paragraph: This section covers various issues raised / detected by the AV QC pipeline. + - link "๐Ÿ“น Multi-Part Interviews Mark parts of the interview to process, or ignore.": + - /url: /issues/multiPart + - heading "๐Ÿ“น Multi-Part Interviews" [level=4] + - paragraph: Mark parts of the interview to process, or ignore. + - link "๐Ÿ“น Multi-Combined Audio Interviews Help identify the audio files that should be transcribed (and what should be ignored)": + - /url: /issues/multiCombinedAudio + - heading "๐Ÿ“น Multi-Combined Audio Interviews" [level=4] + - paragraph: Help identify the audio files that should be transcribed (and what should be ignored) + - link "๐ŸŽง Unlabelled Diarized Audio Label unlabelled audio files with the correct roles, for further downstream processing.": + - /url: /issues/unlabelledAudio + - heading "๐ŸŽง Unlabelled Diarized Audio" [level=4] + - paragraph: Label unlabelled audio files with the correct roles, for further downstream processing. + - link "๐Ÿ“ Missing Interviews List interviews with Runsheets marked as conducted, but no data associated with them.": + - /url: /issues/missing + - heading "๐Ÿ“ Missing Interviews" [level=4] + - paragraph: List interviews with Runsheets marked as conducted, but no data associated with them. + - link "๐Ÿ“œ Missing Transcripts List interviews with video / audio data, but no associated transcripts.": + - /url: /issues/noTranscript + - heading "๐Ÿ“œ Missing Transcripts" [level=4] + - paragraph: List interviews with video / audio data, but no associated transcripts. + - link "๐Ÿ“œ Missing Runsheets List interviews with video / audio data, but no associated runsheets.": + - /url: /issues/noRunsheet + - heading "๐Ÿ“œ Missing Runsheets" [level=4] + - paragraph: List interviews with video / audio data, but no associated runsheets. + - link "๐ŸŽ™๏ธ Failed Audio QC Combined audio that failed pre-transcription QC (silence, clipping, voice activity) and is not being transcribed.": + - /url: /issues/audioQcFailed + - heading "๐ŸŽ™๏ธ Failed Audio QC" [level=4] + - paragraph: Combined audio that failed pre-transcription QC (silence, clipping, voice activity) and is not being transcribed. + - link "๐Ÿ“ค Pending Transcription Push Audio that passed QC but has not yet been pushed to TranscribeMe.": + - /url: /issues/pendingTranscriptionPush + - heading "๐Ÿ“ค Pending Transcription Push" [level=4] + - paragraph: Audio that passed QC but has not yet been pushed to TranscribeMe. + - link "โณ Awaiting Vendor Transcription Audio pushed to TranscribeMe with no transcript delivered back yet.": + - /url: /issues/awaitingVendorTranscription + - heading "โณ Awaiting Vendor Transcription" [level=4] + - paragraph: Audio pushed to TranscribeMe with no transcript delivered back yet. + - link "๐Ÿ“ฅ Transcript Not Imported Transcripts delivered by TranscribeMe that have not yet appeared in transcript_files.": + - /url: /issues/transcriptNotImported + - heading "๐Ÿ“ฅ Transcript Not Imported" [level=4] + - paragraph: Transcripts delivered by TranscribeMe that have not yet appeared in transcript_files. + - link "๐Ÿงพ Pipeline Failures Errors raised across pipeline stages/crawlers, with occurrence counts and resolution tracking.": + - /url: /issues/pipelineFailures + - heading "๐Ÿงพ Pipeline Failures" [level=4] + - paragraph: Errors raised across pipeline stages/crawlers, with occurrence counts and resolution tracking. + - link "๐Ÿ”— Runsheet Match Match malformed interview files (datetime_parse failures) to missing runsheet entries, subject by subject.": + - /url: /issues/runsheetMatch + - heading "๐Ÿ”— Runsheet Match" [level=4] + - paragraph: Match malformed interview files (datetime_parse failures) to missing runsheet entries, subject by subject. + - link "๐Ÿ“‹ Override Ledger Audit trail of manual overrides (audio QC bypasses, runsheet datetime matches) - which files were addressed and by what mechanism.": + - /url: /issues/overrideLedger + - heading "๐Ÿ“‹ Override Ledger" [level=4] + - paragraph: Audit trail of manual overrides (audio QC bypasses, runsheet datetime matches) - which files were addressed and by what mechanism. + - paragraph: This project is under active development. If you need more issues catalogued, please reach out to developers. + `); + }); + test('main + breadcrumbs snapshot', async ({page}) => { + await expect(page.locator('body')).toMatchAriaSnapshot(` + - text: Navigation + - list: + - listitem: + - link "Issues": + - /url: /issues + - img + - text: "" + - button "Toggle" [expanded]: + - img + - text: "" + - list: + - listitem: + - link "Multi-Part Interviews": + - /url: /issues/multiPart + - listitem: + - link "Multi-Combined Audio Files": + - /url: /issues/multiCombinedAudio + - listitem: + - link "Unlabelled Audio": + - /url: /issues/unlabelledAudio + - listitem: + - link "Missing Interviews": + - /url: /issues/missing + - listitem: + - link "Missing Runsheets": + - /url: /issues/noRunsheet + - listitem: + - link "Missing Transcripts": + - /url: /issues/noTranscript + - listitem: + - link "Failed Audio QC": + - /url: /issues/audioQcFailed + - listitem: + - link "Pending Transcription Push": + - /url: /issues/pendingTranscriptionPush + - listitem: + - link "Awaiting Vendor Transcription": + - /url: /issues/awaitingVendorTranscription + - listitem: + - link "Transcript Not Imported": + - /url: /issues/transcriptNotImported + - listitem: + - link "Pipeline Failures": + - /url: /issues/pipelineFailures + - listitem: + - link "Runsheet Match": + - /url: /issues/runsheetMatch + - listitem: + - link "Override Ledger": + - /url: /issues/overrideLedger + - listitem: + - link "Interviews": + - /url: /interviews + - img + - text: "" + - button "Toggle": + - img + - text: "" + - listitem: + - link "Audio Journals": + - /url: /journals + - img + - text: "" + `); + await expect(page.getByRole('main')).toMatchAriaSnapshot(` + - heading "๐Ÿ“น Multi-Part Interviews" [level=4] + - paragraph: Mark parts of the interview to process, or ignore. + `); + }); + + test('test issue type links', async ({page}) => { + await expect(page.getByRole('link', {name: '๐Ÿ“น Multi-Combined Audio'})).toBeVisible(); + await expect(page.getByRole('link', {name: '๐ŸŽง Unlabelled Diarized Audio'})).toBeVisible(); + await expect(page.getByRole('link', {name: '๐Ÿ“ Missing Interviews List'})).toBeVisible(); + await expect(page.getByRole('link', {name: '๐ŸŽ™๏ธ Failed Audio QC Combined'})).toBeVisible(); + await expect(page.getByRole('link', {name: '๐Ÿ“ค Pending Transcription Push'})).toBeVisible(); + await expect(page.getByRole('link', {name: 'โณ Awaiting Vendor'})).toBeVisible(); + await expect(page.getByRole('link', {name: '๐Ÿ“ฅ Transcript Not Imported'})).toBeVisible(); + await expect(page.getByRole('link', {name: '๐Ÿงพ Pipeline Failures Errors'})).toBeVisible(); + await expect(page.getByRole('link', {name: '๐Ÿ”— Runsheet Match Match'})).toBeVisible(); + await expect(page.getByRole('link', {name: '๐Ÿ“‹ Override Ledger Audit'})).toBeVisible(); + }); +}); \ No newline at end of file diff --git a/tests/issues/multiPart.spec.ts b/tests/issues/multiPart.spec.ts new file mode 100644 index 0000000..0f51074 --- /dev/null +++ b/tests/issues/multiPart.spec.ts @@ -0,0 +1,38 @@ +import {expect} from '@playwright/test'; +import {test} from "@tests/playwright"; + +test.describe("landing page", async () => { + test.beforeEach(async ({page}) => { + await page.goto("/issues/multiPart"); + }); + + test("info text", async ({page}) => { + await expect(page.getByRole('heading', {name: 'Interviews with Multiple Parts', level: 2})).toBeVisible(); + await expect(page.getByRole("paragraph") + .filter({has: page.getByText(/The following \d+ interviews have multiple parts:/)})) + .toBeAttached(); + }); + + test.describe("results table", async () => { + + test("controls menu present", async ({page}) => { + await expect(page.getByRole('button', {name: 'Select columns'})).toBeVisible(); + await expect(page.getByRole('button', {name: 'Show filters'})).toBeVisible(); + await expect(page.getByRole('button', {name: 'Density'})).toBeVisible(); + await expect(page.getByRole('button', {name: 'Export'})).toBeVisible(); + }); + + test("table headers present", async ({page}) => { + const gridElement = page.getByRole("grid"); + await expect(gridElement).toBeAttached(); + await expect(gridElement.getByRole('columnheader', {name: 'Interview Name'})).toBeVisible(); + await expect(gridElement.getByRole('columnheader', {name: 'Interview Type'})).toBeVisible(); + await expect(gridElement.getByRole('columnheader', {name: 'Subject ID'})).toBeVisible(); + await expect(gridElement.getByRole('columnheader', {name: 'Parts Count'})).toBeVisible(); + }); + }); + + test("sidebar", async ({page}) => { + await expect(page.getByRole('navigation', {name: 'sidebar'})).toMatchAriaSnapshot(); + }); +}); \ No newline at end of file diff --git a/tests/journals.spec.ts b/tests/journals.spec.ts new file mode 100644 index 0000000..21d8743 --- /dev/null +++ b/tests/journals.spec.ts @@ -0,0 +1,21 @@ +import {expect} from '@playwright/test'; +import {test} from "@tests/playwright"; + +test.describe("Audio journals page", async () => { + // test("",async ()=>{}); + + test("breadcrumbs match snapshot", async ({journals}) => { + await expect(journals.breadcrumbs).toMatchAriaSnapshot(); + }); + + test("sidebar matches snapshot", async ({journals}) => { + await expect(journals.sidebar).toMatchAriaSnapshot(); + }); + + test.describe("main content", async () => { + test("is present",async ({journals})=>{ + await expect(journals.main).toBeInViewport(); + }); + }); + +}); \ No newline at end of file diff --git a/tests/pages/home.ts b/tests/pages/home.ts new file mode 100644 index 0000000..e0e2cc6 --- /dev/null +++ b/tests/pages/home.ts @@ -0,0 +1,16 @@ +import {Page} from '@playwright/test'; +import {AbstractPageModel} from "@tests/pages/page"; + +export class HomePageModel extends AbstractPageModel { + + static readonly path = "/"; + + constructor(page: Page) { + super(page); + } + + async goto() { + return this.page.goto(HomePageModel.path); + } + +} \ No newline at end of file diff --git a/tests/pages/index.ts b/tests/pages/index.ts new file mode 100644 index 0000000..4f603d3 --- /dev/null +++ b/tests/pages/index.ts @@ -0,0 +1,11 @@ +import {PageModel} from "./page"; +import {HomePageModel} from "./home"; +import {InterviewsPageModel} from "./interviews"; +import {JournalsPage} from "./journals"; + +export { + HomePageModel, + InterviewsPageModel, + JournalsPage, + type PageModel, +}; \ No newline at end of file diff --git a/tests/pages/interviews.ts b/tests/pages/interviews.ts new file mode 100644 index 0000000..346bb72 --- /dev/null +++ b/tests/pages/interviews.ts @@ -0,0 +1,39 @@ +import {Locator, Page} from '@playwright/test'; +import {AbstractPageModel} from "@tests/pages/page"; + + +export class InterviewsPageModel extends AbstractPageModel { + + static readonly path = "/interviews"; + + readonly selectColumnsButton: Locator; + readonly filtersButton: Locator; + readonly densityButton: Locator; + readonly exportButton: Locator; + + readonly selectRowHeader: Locator; + readonly nameColumn: Locator; + readonly typeColumn: Locator; + readonly subjectColumn: Locator; + readonly studyColumn: Locator; + + constructor(page: Page) { + super(page); + + this.selectColumnsButton = page.getByRole('button', {name: 'Select columns'}); + this.filtersButton = page.getByRole('button', {name: 'Show filters'}); + this.densityButton = page.getByRole('button', {name: 'Density'}); + this.exportButton = page.getByRole('button', {name: 'Export'}); + this.selectRowHeader = page.getByRole('checkbox', {name: 'Select all rows'}); + + this.nameColumn = page.getByRole('columnheader', {name: 'Interview Name'}); + this.typeColumn = page.getByRole('columnheader', {name: 'Interview Type'}); + this.subjectColumn = page.getByRole('columnheader', {name: 'Subject ID'}); + this.studyColumn = page.getByRole('columnheader', {name: 'Study ID'}); + } + + async goto() { + return this.page.goto(InterviewsPageModel.path); + } + +} \ No newline at end of file diff --git a/tests/pages/journals.ts b/tests/pages/journals.ts new file mode 100644 index 0000000..65cd1ea --- /dev/null +++ b/tests/pages/journals.ts @@ -0,0 +1,16 @@ +import {Locator, Page} from "@playwright/test"; +import {AbstractPageModel} from "@tests/pages/page"; + +export class JournalsPage extends AbstractPageModel { + + constructor(page: Page) { + super(page); + } + + async goto() { + return this.page.goto("/journals") + } + + + +} \ No newline at end of file diff --git a/tests/pages/page.ts b/tests/pages/page.ts new file mode 100644 index 0000000..cd61bd0 --- /dev/null +++ b/tests/pages/page.ts @@ -0,0 +1,31 @@ +import {Locator, Page, Response} from "@playwright/test"; + + +export interface PageModel { + + readonly page: Page; + readonly breadcrumbs: Locator; + readonly sidebar: Locator; + readonly main: Locator; + + goto() : Promise + +} + +export abstract class AbstractPageModel implements PageModel { + readonly breadcrumbs: Locator; + readonly main: Locator; + readonly sidebar: Locator; + readonly page: Page; + + protected constructor(page: Page) { + this.page = page; + this.breadcrumbs = page.locator('header'); + this.sidebar = page.getByRole("navigation",{name:"sidebar"}); + this.main = page.getByRole('main'); + + } + + abstract goto(): Promise; + +} \ No newline at end of file diff --git a/tests/playwright.ts b/tests/playwright.ts new file mode 100644 index 0000000..d85a8b8 --- /dev/null +++ b/tests/playwright.ts @@ -0,0 +1,31 @@ +import {test as baseTest} from '@playwright/test'; +import {HomePageModel, InterviewsPageModel, JournalsPage, PageModel} from "./pages"; + +interface PageFixtures { + interviews: InterviewsPageModel; + journals: JournalsPage, + home: HomePageModel, +} + +type UseFunction = (x: T) => void; + +export const test = baseTest.extend({ + interviews: async ({page}, use: UseFunction) => { + const interview = new InterviewsPageModel(page); + await interview.goto(); + use(interview); + // clean up + }, + journals: async ({page}, use: UseFunction) => { + const journalsPage = new JournalsPage(page); + await journalsPage.goto(); + use(journalsPage); + // clean up + }, + home: async ({page}, use: UseFunction) => { + const homePage = new HomePageModel(page); + await homePage.goto(); + use(homePage); + // clean up + }, +}) \ No newline at end of file diff --git a/tsconfig.json b/tsconfig.json index d7e05e5..8695abc 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,7 +1,11 @@ { "compilerOptions": { "target": "ES2020", - "lib": ["dom", "dom.iterable", "esnext"], + "lib": [ + "dom", + "dom.iterable", + "esnext" + ], "allowJs": true, "skipLibCheck": true, "strict": true, @@ -19,9 +23,23 @@ } ], "paths": { - "@/*": ["./src/*"] + "@/*": [ + "./src/*" + ], + "@tests/*": [ + "./tests/*" + ] } }, - "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"], - "exclude": ["node_modules"] + "include": [ + "next-env.d.ts", + "src/**/*.ts", + "src/**/*.tsx", + "tests/**/*.ts", + "tests/**/*.tsx", + ".next/types/**/*.ts" + ], + "exclude": [ + "node_modules" + ] }